예제 #1
0
def add_members_to_forum(request=None):
    if request and not request.user.is_superuser:
        return HttpResponseForbidden('Not authenticated')

    str = 'Added these users:<br/><br/>\n'

    for portal in CosinnusPortal.objects.all():
        users = get_user_model().objects.filter(id__in=portal.members)
        for group_slug in get_default_user_group_slugs():
            try:
                group = CosinnusGroup.objects.get(slug=group_slug,
                                                  portal_id=portal.id)
                for user in users:
                    memb, created = CosinnusGroupMembership.objects.get_or_create(
                        user=user,
                        group=group,
                        defaults={'status': MEMBERSHIP_MEMBER})
                    if not created:
                        if memb.status == MEMBERSHIP_PENDING:
                            memb.status = MEMBERSHIP_MEMBER
                            memb.save()
                            str += 'Set user %d to not pending anymore in portal %d <br/>\n' % (
                                user.id, portal.id)
                    else:
                        str += 'Added user %d to forum in portal %d<br/>\n' % (
                            user.id, portal.id)

            except CosinnusGroup.DoesNotExist:
                str += 'Could not find forum in portal %d <br/>\n' % portal.id

    return HttpResponse(str)
예제 #2
0
def convert_email_group_invites(sender, profile, **kwargs):
    """ Converts all `CosinnusUnregisterdUserGroupInvite` to `CosinnusGroupMembership` pending invites
        for a user after registration. If there were any, also adds an entry to the user's profile's visit-next setting. """
    # TODO: caching?
    user = profile.user
    invites = CosinnusUnregisterdUserGroupInvite.objects.filter(email=get_newly_registered_user_email(user))
    if invites:
        with transaction.atomic():
            other_invites = []
            for invite in invites:
                # skip inviting to auto-invite groups, users are in them automatically
                if invite.group.slug in get_default_user_group_slugs():
                    continue
                # check if the inviting user may invite directly
                if invite.invited_by_id in invite.group.admins:
                    CosinnusGroupMembership.objects.create(group=invite.group, user=user, status=MEMBERSHIP_INVITED_PENDING)
                else:
                    other_invites.append(invite.group.id)
            # trigger translation indexing
            _('Welcome! You were invited to the following projects and groups. Please click the dropdown button to accept or decline the invitation for each of them!')
            msg = 'Welcome! You were invited to the following projects and groups. Please click the dropdown button to accept or decline the invitation for each of them!'
            # create a user-settings-entry
            if other_invites:
                profile.settings['group_recruits'] = other_invites
            profile.add_redirect_on_next_page(reverse('cosinnus:invitations'), msg)
예제 #3
0
    def get_group_clusters(self, user, sort_by_activity=False):
        clusters = []
        projects = list(CosinnusProject.objects.get_for_user(user))
        societies = list(CosinnusSociety.objects.get_for_user(user))
        group_ct = ContentType.objects.get_for_model(
            get_cosinnus_group_model())
        if sort_by_activity:
            group_last_visited_qs = LastVisitedObject.objects.filter(
                user=user,
                content_type=group_ct,
                portal=CosinnusPortal.get_current())
            # a dict of group-id -> datetime
            group_last_visited = dict(
                group_last_visited_qs.values_list('object_id', 'visited'))
        else:
            group_last_visited = {}
        default_date = now() - relativedelta(years=100)

        class AttrList(list):
            last_visited = None

        for society in societies:
            if society.slug in get_default_user_group_slugs():
                continue

            # the most recent visit time to any project or society in the cluster
            most_recent_dt = group_last_visited.get(society.id, default_date)
            items = AttrList([DashboardItem(society, is_emphasized=True)])
            for i in range(len(projects) - 1, -1, -1):
                project = projects[i]
                if project.parent == society:
                    items.append(DashboardItem(project))
                    projects.pop(i)
                    project_dt = group_last_visited.get(
                        project.id, default_date)
                    most_recent_dt = project_dt if project_dt > most_recent_dt else most_recent_dt
            items.last_visited = most_recent_dt
            clusters.append(items)

        # add unclustered projects as own cluster
        for proj in projects:
            items = AttrList([DashboardItem(proj)])
            items.last_visited = group_last_visited.get(proj.id, default_date)
            clusters.append(items)

        # sort clusters by last_visited
        if sort_by_activity:
            clusters = sorted(clusters,
                              key=lambda cluster: cluster.last_visited,
                              reverse=True)

        return clusters
예제 #4
0
    def __init__(self, obj=None, is_emphasized=False, user=None):
        if obj:
            from cosinnus.templatetags.cosinnus_tags import full_name
            if is_emphasized:
                self['is_emphasized'] = is_emphasized
            # smart conversion by known models
            if type(obj) is get_cosinnus_group_model() or issubclass(obj.__class__, get_cosinnus_group_model()):
                self['icon'] = obj.get_icon()
                self['text'] = escape(obj.name)
                self['url'] = obj.get_absolute_url()
            elif type(obj) is CosinnusIdea:
                self['icon'] = obj.get_icon()
                self['text'] = escape(obj.title)
                self['url'] = obj.get_absolute_url()
            elif type(obj) is CosinnusOrganization:
                self['icon'] = 'fa-building'
                self['text'] = escape(obj.name)
                self['url'] = obj.get_absolute_url()
            elif isinstance(obj, NextcloudFileProxy):
                self['icon'] = 'fa-cloud'
                self['text'] = obj.name
                self['url'] = obj.url
                self['subtext'] = obj.excerpt
            elif obj._meta.model.__name__ == 'Message' and not settings.COSINNUS_ROCKET_ENABLED and not 'cosinnus_message' in settings.COSINNUS_DISABLED_COSINNUS_APPS:
                self['icon'] = 'fa-envelope'
                self['text'] = escape(obj.subject)
                self['url'] = reverse('postman:view_conversation', kwargs={'thread_id': obj.thread_id}) if obj.thread_id else obj.get_absolute_url()
                self['subtext'] = escape(', '.join([full_name(participant) for participant in obj.other_participants(user)]))
            elif issubclass(obj.__class__, BaseUserProfile):
                self['icon'] = obj.get_icon()
                self['text'] = escape(full_name(obj.user))
                self['url'] = obj.get_absolute_url()
            elif BaseTaggableObjectModel in inspect.getmro(obj.__class__):
                
                
                self['icon'] = 'fa-question'
                self['text'] = escape(obj.get_readable_title())
                self['url'] = obj.get_absolute_url()
                self['subtext'] = escape(obj.group.name)

                if hasattr(obj, 'get_icon'):
                    self['icon'] = obj.get_icon()
                if obj.group.slug in get_default_user_group_slugs():
                    self['group'] = escape(CosinnusPortal.get_current().name)
                else:
                    self['group'] = escape(obj.group.name)
                    self['group_icon'] = obj.group.get_icon()
                if obj.__class__.__name__ == 'Event':
                    if obj.state != 2:
                        self['subtext'] = {'is_date': True, 'date': django_date_filter(obj.from_date, 'Y-m-d')}
예제 #5
0
def ensure_user_to_default_portal_groups(sender, created, **kwargs):
    """ Whenever a portal membership changes, make sure the user is in the default groups for this Portal """
    try:
        from cosinnus.models.group import CosinnusGroupMembership, MEMBERSHIP_MEMBER
        membership = kwargs.get('instance')
        CosinnusGroup = get_cosinnus_group_model()
        for group_slug in get_default_user_group_slugs():
            try:
                group = CosinnusGroup.objects.get(
                    slug=group_slug, portal_id=membership.group.id)
                CosinnusGroupMembership.objects.get_or_create(
                    user=membership.user,
                    group=group,
                    defaults={'status': MEMBERSHIP_MEMBER})
            except CosinnusGroup.DoesNotExist:
                continue

    except:
        # We fail silently, because we never want to 500 here unexpectedly
        logger.error(
            "Error while trying to add User Membership for newly created user."
        )
예제 #6
0
def map_search_endpoint(request, filter_group_id=None):
    """ Maps API search endpoint using haystack search results. For parameters see ``MAP_SEARCH_PARAMETERS``
        returns JSON with the contents of type ``HaystackMapResult``
        
        @param filter_group_id: Will filter all items by group relation, where applicable 
                (i.e. users are filtered by group memberships for that group, events as events in that group)
    """
    implicit_ignore_location = not any([
        loc_param in request.GET
        for loc_param in ['sw_lon', 'sw_lat', 'ne_lon', 'ne_lat']
    ])
    params = _collect_parameters(request.GET, MAP_SEARCH_PARAMETERS)
    query = force_text(params['q'])
    limit = params['limit']
    page = params['page']
    item_id = params['item']
    prefer_own_portal = getattr(settings, 'MAP_API_HACKS_PREFER_OWN_PORTAL',
                                False)

    if not is_number(limit) or limit < 0:
        return HttpResponseBadRequest(
            '``limit`` param must be a positive number or 0!')
    limit = min(limit, SERVER_SIDE_SEARCH_LIMIT)
    if not is_number(page) or page < 0:
        return HttpResponseBadRequest(
            '``page`` param must be a positive number or 0!')

    # filter for requested model types
    model_list = [
        klass for klass, param_name in list(SEARCH_MODEL_NAMES.items())
        if params.get(param_name, False)
    ]
    sqs = SearchQuerySet().models(*model_list)
    # filter for map bounds (Points are constructed ith (lon, lat)!!!)
    if not params['ignore_location'] and not implicit_ignore_location:
        sqs = sqs.within('location', Point(params['sw_lon'], params['sw_lat']),
                         Point(params['ne_lon'], params['ne_lat']))
    # filter for user's own content
    if params['mine'] and request.user.is_authenticated:
        user_id = request.user.id
        sqs = sqs.filter_and(
            Q(creator=user_id) | Q(user_id=user_id) | Q(group_members=user_id))
    # filter for search terms
    if query:
        sqs = sqs.auto_query(query)
    # group-filtered-map view for on-group pages
    if filter_group_id:
        group = get_object_or_None(get_cosinnus_group_model(),
                                   id=filter_group_id)
        if group:
            filtered_groups = [filter_group_id]
            # get child projects of this group
            filtered_groups += [
                subproject.id for subproject in group.get_children()
                if subproject.is_active
            ]
            sqs = sqs.filter_and(
                Q(membership_groups__in=filtered_groups)
                | Q(group__in=filtered_groups))
    # filter topics
    topics = ensure_list_of_ints(params.get('topics', ''))
    if topics:
        sqs = sqs.filter_and(mt_topics__in=topics)
    # filter for portal visibility
    sqs = filter_searchqueryset_for_portal(
        sqs, restrict_multiportals_to_current=prefer_own_portal)
    # filter for read access by this user
    sqs = filter_searchqueryset_for_read_access(sqs, request.user)
    # filter events by upcoming status
    if params['events'] and Event is not None:
        sqs = filter_event_searchqueryset_by_upcoming(sqs)

    # filter all default user groups if the new dashboard is being used (they count as "on plattform" and aren't shown)
    if getattr(settings, 'COSINNUS_USE_V2_DASHBOARD', False):
        sqs = sqs.exclude(is_group_model=True,
                          slug__in=get_default_user_group_slugs())

    # if we hae no query-boosted results, use *only* our custom sorting (haystack's is very random)
    if not query:
        if prefer_own_portal:
            sqs = sqs.order_by('-portal', '-local_boost')
        else:
            sqs = sqs.order_by('-local_boost')

    # sort results into one list per model
    total_count = sqs.count()
    sqs = sqs[limit * page:limit * (page + 1)]
    results = []
    for result in sqs:
        # if we hae no query-boosted results, use *only* our custom sorting (haystack's is very random)
        if not query:
            result.score = result.local_boost
            if prefer_own_portal and is_number(result.portal) and int(
                    result.portal) == CosinnusPortal.get_current().id:
                result.score += 100.0
        results.append(HaystackMapResult(result, user=request.user))

    # if the requested item (direct select) is not in the queryset snippet
    # (might happen because of an old URL), then mix it in as first item and drop the last
    if item_id:
        item_id = str(item_id)
        if not any([res['id'] == item_id for res in results]):
            item_result = get_searchresult_by_itemid(item_id, request.user)
            if item_result:
                results = [HaystackMapResult(item_result, user=request.user)
                           ] + results[:-1]

    page_obj = None
    if results:
        page_obj = {
            'index': page,
            'count': len(results),
            'total_count': total_count,
            'start': (limit * page) + 1,
            'end': (limit * page) + len(results),
            'has_next': total_count > (limit * (page + 1)),
            'has_previous': page > 0,
        }

    data = {
        'results': results,
        'page': page_obj,
    }
    return JsonResponse(data)
예제 #7
0
    def __init__(self, obj=None, is_emphasized=False, user=None):
        if obj:
            if is_emphasized:
                self['is_emphasized'] = is_emphasized
            # smart conversion by known models
            if type(obj) is get_cosinnus_group_model() or issubclass(
                    obj.__class__, get_cosinnus_group_model()):
                self[
                    'icon'] = 'fa-sitemap' if obj.type == CosinnusGroup.TYPE_SOCIETY else 'fa-group'
                self['text'] = escape(obj.name)
                self['url'] = obj.get_absolute_url()
            elif type(obj) is CosinnusIdea:
                self['icon'] = 'fa-lightbulb-o'
                self['text'] = escape(obj.title)
                self['url'] = obj.get_absolute_url()
            elif obj._meta.model.__name__ == 'Message' and not settings.COSINNUS_ROCKET_ENABLED:
                self['icon'] = 'fa-envelope'
                self['text'] = escape(obj.subject)
                self['url'] = reverse(
                    'postman:view_conversation',
                    kwargs={'thread_id': obj.thread_id
                            }) if obj.thread_id else obj.get_absolute_url()
                self['subtext'] = escape(', '.join([
                    full_name(participant)
                    for participant in obj.other_participants(user)
                ]))
            elif issubclass(obj.__class__, BaseUserProfile):
                self['icon'] = 'fa-user'
                self['text'] = escape(full_name(obj.user))
                self['url'] = obj.get_absolute_url()
            elif BaseTaggableObjectModel in inspect.getmro(obj.__class__):
                self['icon'] = 'fa-question'
                self['text'] = escape(obj.title)
                self['url'] = obj.get_absolute_url()
                self['subtext'] = escape(obj.group.name)

                if obj.group.slug in get_default_user_group_slugs():
                    self['group'] = escape(CosinnusPortal.get_current().name)
                else:
                    self['group'] = escape(obj.group.name)
                    self[
                        'group_icon'] = 'fa-group' if obj.group.type == CosinnusGroup.TYPE_PROJECT else 'fa-sitemap'

                if obj.__class__.__name__ == 'Event':
                    if obj.state == 2:
                        self['icon'] = 'fa-calendar-check-o'
                    else:
                        self['subtext'] = {
                            'is_date': True,
                            'date': django_date_filter(obj.from_date, 'Y-m-d')
                        }
                        self['icon'] = 'fa-calendar'
                if obj.__class__.__name__ == 'Etherpad':
                    self['icon'] = 'fa-file-text'
                if obj.__class__.__name__ == 'Ethercalc':
                    self['icon'] = 'fa-table'
                if obj.__class__.__name__ == 'FileEntry':
                    self['icon'] = 'fa-file'
                if obj.__class__.__name__ == 'Message':
                    self['icon'] = 'fa-envelope'
                if obj.__class__.__name__ == 'TodoEntry':
                    self['icon'] = 'fa-tasks'
                if obj.__class__.__name__ == 'Poll':
                    self['icon'] = 'fa-bar-chart'
                if obj.__class__.__name__ == 'Offer':
                    self['icon'] = 'fa-exchange-alt'
예제 #8
0
    def fetch_queryset_for_user(self,
                                model,
                                user,
                                sort_key=None,
                                only_mine=True,
                                only_mine_strict=True,
                                current_only=True):
        """ Retrieves a queryset of sorted content items for a user, for a given model.
            @param model: An actual model class. Supported are all `BaseTaggableObjectModel`s,
                `CosinnusIdea`, and `postman.Message`
            @param user: Querysets are filtered by view permission for this user
            @param sort_key: (optional) the key for the `order_by` clause for the queryset
            @param only_mine: if True, will only show objects that belong to groups or projects
                the `user` is a member of, including the Forum, and including Ideas.
                If False, will include all visible items in this portal for the user. 
            @param only_mine_strict: If set to True along with `only_mine`, really only objects
                from the user's groups and projects will be returned, *excluding* the Forum and
                Ideas.
            @param current_only: if True, will only retrieve current items (ie, upcoming events) 
                TODO: is this correct?
        """

        ct = ContentType.objects.get_for_model(model)
        model_name = '%s.%s' % (ct.app_label, ct.model_class().__name__)

        # ideas are excluded in strict mode
        if model is CosinnusIdea and only_mine and only_mine_strict:
            return model.objects.none()

        queryset = None
        skip_filters = False
        if BaseHierarchicalTaggableObjectModel in inspect.getmro(model):
            queryset = model._default_manager.filter(is_container=False)
            queryset = exclude_special_folders(queryset)
        elif model_name == 'cosinnus_event.Event':
            if current_only:
                queryset = model.objects.all_upcoming()
            else:
                queryset = model.objects.get_queryset()
            queryset = queryset.exclude(is_hidden_group_proxy=True)
        elif model_name == 'cosinnus_marketplace.Offer':
            queryset = model.objects.all_active()
        elif model is CosinnusIdea or BaseTaggableObjectModel in inspect.getmro(
                model):
            queryset = model._default_manager.all()
        elif model_name == "postman.Message":
            queryset = model.objects.inbox(user)
            skip_filters = True
        elif model is get_cosinnus_group_model() or issubclass(
                model, get_cosinnus_group_model()):
            queryset = model.objects.get_queryset()
        else:
            return None

        assert queryset is not None
        if not skip_filters:
            # mix in reflected objects
            if model_name.lower() in settings.COSINNUS_REFLECTABLE_OBJECTS and \
                        BaseTaggableObjectModel in inspect.getmro(model):
                mixin = MixReflectedObjectsMixin()
                queryset = mixin.mix_queryset(queryset, model, None, user)

            portal_id = CosinnusPortal.get_current().id
            portal_list = [portal_id]
            if False:
                # include all other portals in pool
                portal_list += getattr(
                    settings, 'COSINNUS_SEARCH_DISPLAY_FOREIGN_PORTALS', [])

            if model is CosinnusIdea or model is get_cosinnus_group_model(
            ) or issubclass(model, get_cosinnus_group_model()):
                queryset = queryset.filter(portal__id__in=portal_list)
            else:
                queryset = queryset.filter(group__portal__id__in=portal_list)
                user_group_ids = get_cosinnus_group_model(
                ).objects.get_for_user_pks(user)

                # in strict mode, filter any content from the default groups as well
                if only_mine and only_mine_strict:
                    exclude_slugs = get_default_user_group_slugs()
                    if exclude_slugs:
                        exclude_groups = get_cosinnus_group_model(
                        ).objects.get_cached(slugs=exclude_slugs,
                                             portal_id=portal_id)
                        exclude_group_ids = [
                            group.id for group in exclude_groups
                        ]
                        user_group_ids = [
                            group_id for group_id in user_group_ids
                            if group_id not in exclude_group_ids
                        ]
                filter_q = Q(group__pk__in=user_group_ids)
                # if the switch is on, also include public posts from all portals
                if not only_mine:
                    filter_q = filter_q | Q(
                        media_tag__visibility=BaseTagObject.VISIBILITY_ALL)
                queryset = queryset.filter(filter_q)

                # filter for read permissions for user
                queryset = filter_tagged_object_queryset_for_user(
                    queryset, user)

            if sort_key:
                queryset = queryset.order_by(sort_key)
            else:
                queryset = queryset.order_by('-created')
        return queryset
예제 #9
0
    def get_group_clusters(self, user, sort_by_activity=False):
        clusters = []

        # collect map of last visited groups
        group_ct = ContentType.objects.get_for_model(
            get_cosinnus_group_model())
        if sort_by_activity:
            group_last_visited_qs = LastVisitedObject.objects.filter(
                user=user,
                content_type=group_ct,
                portal=CosinnusPortal.get_current())
            # a dict of group-id -> datetime
            group_last_visited = dict(
                group_last_visited_qs.values_list('object_id', 'visited'))
        else:
            group_last_visited = {}
        default_date = now() - relativedelta(years=100)

        # collect and sort user projects and societies lists
        projects = list(CosinnusProject.objects.get_for_user(user))
        societies = list(CosinnusSociety.objects.get_for_user(user))
        # sort sub items by last_visited or name
        if sort_by_activity:
            projects = sorted(projects,
                              key=lambda project: group_last_visited.get(
                                  project.id, default_date),
                              reverse=True)
            societies = sorted(societies,
                               key=lambda society: group_last_visited.get(
                                   society.id, default_date),
                               reverse=True)
        else:
            projects = sorted(projects, key=sort_key_strcoll_attr('name'))
            societies = sorted(societies, key=sort_key_strcoll_attr('name'))

        # sort projects into their societies clusters, society clusters are always displayed first
        for society in societies:
            if society.slug in get_default_user_group_slugs():
                continue

            # the most recent visit time to any project or society in the cluster
            most_recent_dt = group_last_visited.get(society.id, default_date)
            items_projects = []
            for i in range(len(projects) - 1, -1, -1):
                project = projects[i]
                if project.parent == society:
                    items_projects.insert(0, DashboardItem(
                        project))  # prepend because of reversed order
                    projects.pop(i)
                    project_dt = group_last_visited.get(
                        project.id, default_date)
                    most_recent_dt = project_dt if project_dt > most_recent_dt else most_recent_dt
            items = [DashboardItem(society, is_emphasized=True)
                     ] + items_projects
            clusters.append(items)

        # add unclustered projects as own cluster
        for proj in projects:
            items = [DashboardItem(proj)]
            clusters.append(items)

        return clusters
예제 #10
0
 def prepare_group_name(self, obj):
     # filter all default user groups if the new dashboard is being used (they count as "on plattform" and aren't shown)
     if getattr(settings, 'COSINNUS_USE_V2_DASHBOARD',
                False) and obj.group.slug in get_default_user_group_slugs():
         return None
     return obj.group.name
예제 #11
0
def map_search_endpoint(request, filter_group_id=None):
    """ Maps API search endpoint using haystack search results. For parameters see ``MAP_SEARCH_PARAMETERS``
        returns JSON with the contents of type ``HaystackMapResult``
        
        @param filter_group_id: Will filter all items by group relation, where applicable 
                (i.e. users are filtered by group memberships for that group, events as events in that group)
    """
    implicit_ignore_location = not any([
        loc_param in request.GET
        for loc_param in ['sw_lon', 'sw_lat', 'ne_lon', 'ne_lat']
    ])
    params = _collect_parameters(request.GET, MAP_SEARCH_PARAMETERS)
    query = force_text(params['q'])
    limit = params['limit']
    page = params['page']
    item_id = params['item']

    if params.get('cloudfiles', False):
        return map_cloudfiles_endpoint(request, query, limit, page)

    # TODO: set to  params['external'] after the external switch button is in frontend!
    external = settings.COSINNUS_EXTERNAL_CONTENT_ENABLED

    prefer_own_portal = getattr(settings, 'MAP_API_HACKS_PREFER_OWN_PORTAL',
                                False)

    if not is_number(limit) or limit < 0:
        return HttpResponseBadRequest(
            '``limit`` param must be a positive number or 0!')
    limit = min(limit, SERVER_SIDE_SEARCH_LIMIT)
    if not is_number(page) or page < 0:
        return HttpResponseBadRequest(
            '``page`` param must be a positive number or 0!')

    # filter for requested model types
    model_list = [
        klass for klass, param_name in list(SEARCH_MODEL_NAMES.items())
        if params.get(param_name, False)
    ]

    sqs = SearchQuerySet().models(*model_list)

    # filter for map bounds (Points are constructed ith (lon, lat)!!!)
    if not params['ignore_location'] and not implicit_ignore_location:
        sqs = sqs.within('location', Point(params['sw_lon'], params['sw_lat']),
                         Point(params['ne_lon'], params['ne_lat']))
    # filter for user's own content
    if params['mine'] and request.user.is_authenticated:
        user_id = request.user.id
        sqs = sqs.filter_and(
            Q(creator=user_id) | Q(user_id=user_id) | Q(group_members=user_id))
    # filter for search terms
    if query:
        sqs = sqs.auto_query(query)

    # group-filtered-map view for on-group pages
    if filter_group_id:
        group = get_object_or_None(get_cosinnus_group_model(),
                                   id=filter_group_id)
        if group:
            filtered_groups = [filter_group_id]
            # get child projects of this group
            filtered_groups += [
                subproject.id for subproject in group.get_children()
                if subproject.is_active
            ]
            sqs = sqs.filter_and(
                Q(membership_groups__in=filtered_groups)
                | Q(group__in=filtered_groups))

    # filter topics
    topics = ensure_list_of_ints(params.get('topics', ''))
    if topics:
        sqs = sqs.filter_and(mt_topics__in=topics)
    if settings.COSINNUS_ENABLE_SDGS:
        sdgs = ensure_list_of_ints(params.get('sdgs', ''))
        if sdgs:
            sqs = sqs.filter_and(sdgs__in=sdgs)
    if settings.COSINNUS_MANAGED_TAGS_ENABLED:
        managed_tags = ensure_list_of_ints(params.get('managed_tags', ''))
        if managed_tags:
            sqs = sqs.filter_and(managed_tags__in=managed_tags)
    # filter for portal visibility
    sqs = filter_searchqueryset_for_portal(
        sqs,
        restrict_multiportals_to_current=prefer_own_portal,
        external=external)
    # filter for read access by this user
    sqs = filter_searchqueryset_for_read_access(sqs, request.user)
    # filter events by upcoming status and exclude hidden proxies
    if params['events'] and Event is not None:
        sqs = filter_event_searchqueryset_by_upcoming(sqs).exclude(
            is_hidden_group_proxy=True)

    # filter all default user groups if the new dashboard is being used (they count as "on plattform" and aren't shown)
    if getattr(settings, 'COSINNUS_USE_V2_DASHBOARD', False):
        sqs = sqs.exclude(is_group_model=True,
                          slug__in=get_default_user_group_slugs())

    # kip score sorting and only rely on natural ordering?
    skip_score_sorting = False
    # if we hae no query-boosted results, use *only* our custom sorting (haystack's is very random)
    if not query:
        sort_args = ['-local_boost']
        # if we only look at conferences, order them by their from_date, future first!
        if prefer_own_portal:
            sort_args = ['-portal'] + sort_args
        """
        # this would be the way to force-sort a content type by a natural ordering instead of score if its the only type being shown
        if params.get('conferences', False) and sum([1 if params.get(content_key, False) else 0 for content_key in MAP_CONTENT_TYPE_SEARCH_PARAMETERS.keys()]) == 1:
            sort_args = ['-from_date'] + sort_args
            skip_score_sorting = True
        sqs = sqs.order_by(*sort_args)
        """

    # sort results into one list per model
    total_count = sqs.count()
    sqs = sqs[limit * page:limit * (page + 1)]
    results = []

    for i, result in enumerate(sqs):
        if skip_score_sorting:
            # if we skip score sorting and only rely on the natural ordering, we make up fake high scores
            result.score = 100000 - (limit * page) - i
        elif not query:
            # if we hae no query-boosted results, use *only* our custom sorting (haystack's is very random)
            result.score = result.local_boost
            if prefer_own_portal and is_number(result.portal) and int(
                    result.portal) == CosinnusPortal.get_current().id:
                result.score += 100.0
        results.append(HaystackMapResult(result, user=request.user))

    # if the requested item (direct select) is not in the queryset snippet
    # (might happen because of an old URL), then mix it in as first item and drop the last
    if item_id:
        item_id = str(item_id)
        if not any([res['id'] == item_id for res in results]):
            item_result = get_searchresult_by_itemid(item_id, request.user)
            if item_result:
                results = [HaystackMapResult(item_result, user=request.user)
                           ] + results[:-1]

    page_obj = None
    if results:
        page_obj = {
            'index': page,
            'count': len(results),
            'total_count': total_count,
            'start': (limit * page) + 1,
            'end': (limit * page) + len(results),
            'has_next': total_count > (limit * (page + 1)),
            'has_previous': page > 0,
        }

    data = {
        'results': results,
        'page': page_obj,
    }
    return JsonResponse(data)