Integrating Haystack in Django-CMS omitting Pages with View-Restrictions - django

I want to integrate haystack with django-cms making a search view. My CMS has pages with view restrictions (only a few authenticated users have access to some pages).
The problem is: when making a search, haystack gives me list with results from all pages, including pages to which the current user has not view permissions.
How can I integrate Haystack in a way that filters results, showing only the ones to which the current user has permissions for? If that's not possible, how to configure haystack letting it index only pages without view-restrictions? Any help is appreciated.

In my solution to this problem I use aldryn_search to do the integration of Haystack and django-cms. aldryn_search returns a listwith results from all pages, including the ones the current user hast not view-permissions for. To resolve this issue I'm inheriting from AldrynSearchView and override the get_queryset method like this:
def get_queryset(self):
queryset = super(IntranetSearchView, self).get_queryset()
for result in queryset.load_all():
page = result.object.page
# Begin: modified copy (queryset.exclude added) of cms.utils.decorators.cms_perms
if page:
if page.login_required and not self.request.user.is_authenticated():
queryset = queryset.exclude(id=result.id)
if not page.has_view_permission(self.request, user=self.request.user):
queryset = queryset.exclude(id=result.id)
# End: Copy
return queryset
using queryset.exclude() to exclude results the current user has not permissions for. After that I inherit from AldrynSearchApphook overriding the urls with my new View and than doing a apphoook_pool.register of the modified Apphook.

Related

Customize Django ListView Pagination

im implementing a Django ListView for a Search View. The search is done using Elasticsearch and django-elasticsearch-dsl. Im actually getting a pagination of just 10 items because thats the amount of contents that Elasticsearh answers by default. I could ask elasticsearch to get all the contents but it would be really unnefficient since im only showing like 50 of them.
Is there any way that Django reads the pagination information from other than the queryset?
This is what my view looks like:
from .elastic import search
class SearchView(ListView):
template_name = "search/index.html"
paginate_by = 50
def get_queryset(self):
keyword = self.kwargs['keyword']
lang = get_language()
return search(keyword, lang)
Edit:
When using a normal queryset from database, django manages the pagination, i assume that using a query for count the total amount of objects and by using the paginate_by parameter. In my case, the queryset that i return on the get_queryset method, is only one page lenght because i get those contents from elasticsearch. Im looking for a way to tell django how much contents the query has in total to let django build the pagination ok.
My search method is a query to elasticsearch that returns a page of contents in a queryset. But it doesnt return all the contents. I have the total amount of search results but i dont know where to put that variable to allow django build the pagination.

override django oscar search app to return only merchant specific products?

I am using Django oscar(2.0.2), where I have made foreign key relation of merchant ids with product table AbstractProduct with data, (https://prnt.sc/s1ssxx) so that I can fetch merchant-specific products from the database. By default oscar returns all the products in the search results, I want to only return the products specific to a merchant's site.
I am using Haystack simple search as suggested in oscar documentation, I have tried overriding the search app like all other apps, I have overridden the search_indexes.py file, but it seems that it never gets called from the FacetedSearchView. I also tried to override the search handlers, but it was also not getting called.
I tried understanding oscar's search functionality, but on the shell, I get a warning,
UserWarning: The model is not registered for search.
warnings.warn('The model %r is not registered for search.' % (model,))
Model '' not handled by the routers. Model class oscar_apps.catalogue.models.Product not handled by the routers.
How can I register the Product model for search?
where will I have to override the query like that:
Product.objects.filter(user_id=1), to return only merchant-specific products while searching for a product?
I know, how to override apps, but could someone give an overview and explain to me the steps that will be required to override the search app, and get the basic sorting functionality working?
if my question is not clear, let me know in the comments so I can improve it.
#yajant, I'm not sure whether my answer might be useful to you, but I have faced the same issue and struggled to find out the solution. Hope this might help someone facing the same issue. Thanks
fork the search app and import oscar search and overwrite FacetedSearchView class get_results method as below
Override the get_results method in your forked search/views.py
def get_results(self):
# We're only interested in products (there might be other contenttypes
# in the Solr index).
qs = super().get_results().models(Product)
qs = qs.filter(user_id=1)
return qs

Wagtail per page user permission

My task is to enable users to edit their own page.
I see how to give permissions to a particular page to a group, but how do I do this for the user?
I'd tried use django guardian:
UserObjectPermission.objects.assign_perm('change_custompage', user, obj=custompage)
but this wasn't given me results
I also found the wagtailcore_grouppagepermission table and realized that the permissions principle is not the same as used in django auth_permission
I want to give permissions to edit, and access wagtail admin, I do not need anything else
In this particular task, wagtail I need because of the struct_blocks, I think this is a terrific concept.
Now I see two options: either an override template and the ability to use all the power of the struct_block outside the wagtail, or something to solve with permissions.
Two possibilities:
Create a group for each user - might not seem like a very sophisticated approach, but easily achievable with a bit of scripting...
Put your users in a group that has add permission, but not edit permission, on the parent page that contains all of your users' pages. In Wagtail's permission model http://docs.wagtail.io/en/v1.10.1/topics/permissions.html, 'add' permission also includes the ability to edit pages that you have created yourself. (If you'd rather have the pages created in advance, rather than having your users create them, then you need to set the owner field of the page record to the relevant user.)
Thanks to the help and advice of #gasman, I did this as follows:
Created two models:
first:
class PersonsIndexPage(RoutablePageMixin, Page):
...
#route(r'^$')
def index_view(self, request):
pages = PersonPage.objects.live().all()
return render(request, 'myapp/person_index_page.html', {
'page': self,
'pages': pages,
})
...
subpage_types = ['PersonPage']
second:
class PersonPage(Page):
...
parent_page_types = ['PersonsIndexPage']
subpage_types = []
For the group add access to wagtail admin, as well as gave permissions to create PersonsIndexPage
Since I create personal pages from Django admin, I created a simple action that creates a page for selected users:
I do not know if bulk_create is possible
def create_person_page(modeladmin, request, queryset):
root_page = PersonIndexPage.objects.first()
for user in queryset:
page = PersonPage(title=username), owner=user)
root_page.add_child(instance=page)
create_person_page.short_description = 'Create person page'

Django multiple dbs - admin search results using a read-only db

I'm using the admin search_fields functionality.
The problem: some of my tables are very big. So search is taking forever, and adding extra load on my production database.
As I'm having a follower of my production db, I though a good idea would be to use the follower as a read-only db, especially for those kind of requests.
So I decided to add a 'read-only' db in settings.DATABASES and surcharge ModelAdmin.get_search_results in my admin classes:
def get_search_results(self, request, queryset, search_term):
queryset, use_distinct = super(ReadOnlyDatabaseAdmin, self)\
.get_search_results(request, queryset, search_term)
queryset = queryset.using('read-only')
return queryset, use_distinct
After this update, I started to get some router errors when trying to set some object as foreign key related object of another object:
Cannot assign "...": the current database router prevents this relation
NB: the read-only database was the same as the default one when I tested and got the aforementioned error, I didn't use the follower yet. I just have set a 'read-only' key in settings.DATABASES, pointing to the same dict as DATABASES['default'].
So the problem is not coming from using a different database, but strictly from the database router.
To give more detail: this error is notably coming from admin actions that are performed when in a admin-search-results page (/admin/app/obj/?q=...).
I figured it's maybe because I replace the queryset object in the method. Maybe this object is actually re-used somewhere else notably in admin actions...? I am currently looking into this.
So I'm interested in:
finding the reason of the error
and/or finding another way of performing admin search requests on a follower database to offload the main database
I guess the answer to the error is to do instead:
if request.method == 'GET':
queryset = queryset.using('read-only')
Indeed, the search results are dont with a GET, while the admin actions are done with a POST.
I will have to check this
This is not exactly you are looking for How to improved query performance in Django admin search on related fields (MySQL), but it can help to optimize the queries.

django rest framework, how to restrict the list of data a user can see?

Suppose you want to show
list of all blogs
list of blogs created by a user
How do I implement them in django-rest-framework?
Do I make two viewsets?
Do I make two get_querysets() somehow?
Or two permissions class?
Edit:
you want to show all blogs in a recent tab.
you want to show a user's blog in his profile page.
you want to show popular blogs in a popular tab.
There could many more list of blogs...
It seems that you'd like to just filter the queryset based on some given parameters. You should look at the django-filter and DRF filtering options. They might be what you really need. Filtering
.../blogs/?owner=1
This will give you all blogs that have "owner" field equal to user with id==1
Another option is to use #list_route decorator inside your viewset like this:
#list_route
def popular(self, request)
.... # Do stuff
return Response(data, status=status.HTTP_200_OK)
This will add a blogs route to your viewset and return whatever you tell it to return.
So going to '../blogs/popular/' will return 'data'