Confused by Django Rest Framework Permissions - django

I have a complex set of permissions that I want to apply to my views, but I'm having a hard time understanding where to put them. Here are my basic permissions.
List all projects:
If the user is the owner and if their account is active
Or if they are a superuser
And the object is not private to a different user
Or they are a member of the assigned group
Problem is that when I add this logic to the permissions classes of the ModelApiViewset, it returns every project. How do I get it to show only the ones that meet the above criteria?
To further illustrate the example, I have 2 accounts right now: my superuser, and a test_user. test_user is not an admin or staff account, and is not part of the manager group. The test_user account is an owner of project 2, but not project 1. But if I use the test_user credentials, I see both project 1 and 2:
[
{
"id": 1,
"name": "Test Private Project",
"slug": "test-private-project",
"description": "Just testing the super private project",
"group": {
"name": "manager",
"id": 1
},
"created_date": "2020-04-20T18:04:20.666564Z",
"modified_date": "2020-04-20T18:04:20.666594Z",
"owner": {
"username": "admin",
"full_name_display": "Administrator",
"photo": null,
"is_active": true,
"id": 1
},
"is_private": true
},
{
"id": 2,
"name": "Test User Project",
"slug": "test-user-project",
"description": "Test project for users",
"group": {
"name": "Users",
"id": 2
},
"created_date": "2020-04-20T20:10:02.068390Z",
"modified_date": "2020-04-20T20:10:02.068429Z",
"owner": {
"username": "test_user",
"full_name_display": "Test User",
"photo": null,
"is_active": true,
"id": 2
},
"is_private": false
}
]
The test_user shouldn't see project 1 because they are not the owner, not an admin, and the project is private to the admin.
view:
class ProjectListViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
authentication_classes = [TokenAuthentication, ]
permission_classes = [IsOwner, IsActive, IsPrivatelyOwned]
permissions:
class IsOwner(BasePermission):
def has_object_permission(self, request, view, obj):
return obj.owner == request.user
class IsActive(BasePermission):
def has_object_permission(self, request, view, obj):
return request.user.is_active
class IsPrivatelyOwned(BasePermission):
def has_object_permission(self, request, view, obj):
if obj.is_private:
if obj.owner == request.user:
return True
return False
return True
I know I'm missing something pretty simple here, but I can't seem to figure it out. Any help would be greatly appreciated. Thanks in advance.

I think you misunderstand what permissions are. Permissions and filters are 2 different things. Permissions control the right to access objects in your database.
From the doc:
Also note that the generic views will only check the object-level permissions for views that retrieve a single model instance. If you require object-level filtering of list views, you'll need to filter the queryset separately. See the filtering documentation for more details.

Related

Django REST framework: SearchFilter doesn't work with 2 and more values in search bar if they are from the same field

I use SearchFilter to search for therapists:
class TherapistSearchListAPIView(generics.ListAPIView):
permission_classes = [IsAuthenticated]
search_fields = ['first_name',
'therapist_profile__skills__title',
'therapist_profile__counselling_areas__title']
filter_backends = [filters.SearchFilter]
queryset = Therapist.objects.all()
serializer_class = TherapistSearchSerializer
Generally It works great (searches even when user enters several values from different fields), but there is a problem:
When several values are entered in search bar, if among these values 2 or more values are from the same field, even though the request matches with any of therapists, the search returns NULL.
Example:
Therapist:
[
{
"first_name": "Thomas",
"skills": [
{
"id": "0658374347844fd7b69b3d033e17f9b1",
"title": "Self-reflection"
},
{
"id": "2c6ab46d4ebb4cb1a46c934f0c30ebbe",
"title": "Patience"
},
{
"id": "f22c8210dd4d4a3299ea8887c1da7c30",
"title": "Flexibility"
}
],
"counselling_areas": [
{
"id": "5fb0c57ced4c41129829b3620076dda4",
"title": "Body dysmorphic disorder"
}
]
]
If I write in search bar "Thomas self-reflection patience", it will return me NULL even though everything matches with the therapist in database. How can I fix this? Is there any solution or I need to write my own search filtering function from the scratch?

Django RestFramework Elastic Search: Timeline API

I'm using django restframework along with elastic search to develop a backend application for a mobile app. I need to develop a timeline API that will load a timeline of posts from other users the user is following. Along with other related poststhat the people they're following may comment.
What is the best implementation method for this problem?
like this
#some_vf
def someview(req, **kw):
query = {
"_source": ["field1", "field2", "field3"],
"query": {
"must": [
{"term": {"username": "zhangsan"}}
],
"should": [
{"term": {"userid": 1}},
{"term": {"followed": "something"}} # other filter with zhangsan
]
},
"sort": [{"timestamp": {"order": "asc"}}],
"size": size,
"from": from_
}
es = Elasticsearch()
data = elasticsearch.helpers.scan(es, ["user_docs_index", "related_posts_index", "followed_some_index"], "_doc")
# or do your serializers
return Response(data)

Django 1.11 - make the whole site read-only for users from a given group (preferably with a fixture)

My question is not the same with View permissions in Django because that one explains how to deal with the issue in Django 2.1 and higher which has a concept of "view" permission while I am working with Django 1.1. which does not.
Django 1.11
I have a group users who should have only read-only access to everything on the site. No restrictions on fields, models, and actial data, only what they can do with it (read-only). I know about possible implementations that suggest doing it "field-by-field" (or "make all fields read-only") and "model-by-model" solution. I am curios if there is a way to do it cleaner, on user group level, or at least on user level.
My views.py so far is default:
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
#login_required
def index(request):
"""View function for home page of site."""
# Render the HTML template index.html with the data in the context variable
return render(request, 'home.html')
Ideally, I'd like to be able to do this with a fixture.
Currently in the fixture, I have my groups defined like this:
{
"model": "auth.group",
"fields": {
"name": "some_group",
"permissions": [
[
"add_somemodel",
"myproject",
"somemodel"
],
[
"change_somemodel",
"myproject",
"somemodel"
],
[
"delete_somemodel",
"myproject",
"somemodel"
]
]
}
}
In Django 2.2 I can do
{
"model": "auth.group",
"fields": {
"name": "some_group",
"permissions": [
[
"view_somemodel",
"myproject",
"somemodel"
]
]
}
}
but in Django 1.11 I have only "add", "delete" and "change" - no "view" option (according to the docs enter link description here). So, is there a way to create a fixture that creates a group that has only read permissions for everything?
In your view you need something like this(Notice that this is an example of how to view a post if you belong to the proper access group):
def post_detail(request, slug=None):
if not request.user.is_staff or not request.user.is_superuser:
raise Http404
instance = get_object_or_404(Post, slug=slug)
share_string = quote_plus(instance.content)
context = {
"title": instance.title,
"instance": instance,
"share_string": share_string,
}
return render(request, "post_detail.html", context)
Pay attention to:
if not request.user.is_staff or not request.user.is_superuser:
raise Http404
Here are some link to the docs that will help you:
How to authenticate users
All attributes to django.contrib.auth
Edit:
I saw your code now, so what you want to achieve can be done like this
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
#login_required
def index(request):
"""View function for home page of site."""
# With that way although a user might be logged in
# but the user might not have access to see the page
if not request.user.is_staff or not request.user.is_superuser:
raise Http404
# Render the HTML template index.html with the data in the context variable
return render(request, 'home.html')
That way a user may be logged in, but if it's not staff member or superuser it want have access to the page.
Thank you everybody for responding. I did not figure out the way to do it with only user/group/permissions config in the db with Django 1.11, maybe it does not exist. Here is what I ended up with (very similar to the first suggestion I hit on SO when I started the research 4 hours ago, with minimal code changes)
Create a fixture for my new group that contains only "change_somemodel" permission and created a user as a member of that group, i.e. no "add_somemodel" and no "delete_somemodel" entries and load it into DB:
[
{
"model": "auth.group",
"fields": {
"name": "<my_new_group>",
"permissions": [
[
"change_<somemodel1>",
"<myproject>",
"<somemodel1>"
],
[
"change_<somemodel2>",
"<myproject>",
"<somemodel2>"
]
]
}
,
{
"model": "auth.user",
"fields": {
"password": "<my_password>",
"last_login": null,
"is_superuser": false,
"username": "<my_username>",
"first_name": "",
"last_name": "",
"email": "",
"is_staff": true,
"is_active": true,
"date_joined": "2019-04-01T14:40:30.249Z",
"groups": [
[
"<my_new_group>"
]
],
"user_permissions": []
}
}
],
This took care of the first part: now when I login as this user I do not have "Add new.." or "Delete" buttons anywhere for any model for my user.
Now, when I load a view for a given instance of some model, I still have the fields editable and I still see "Save", "Save and Add Another" and "Save and Continue" buttons. To take care of this, in admin.py in the superclass from which all my models are subclassed, in its custom def changeform_view I added:
if request.user.groups.filter(name='<my_new_group>'):
extra_context['show_save_and_add_another'] = False
extra_context['show_save_and_continue'] = False
extra_context['show_save'] = False
This made those 3 "Save" buttons disappear for all the models and made all fields read-only. Now this new user can not add, delete or edit anything for any model yet they can see everything, just as I wanted.
Looks like in the newer Django, starting from 2.1, this can be done even better.

Django DRF: Schema for bulk creation api

I'm using django-rest-framework to build my API in which supports bulk create/update.
In these cases, the api will accept a list of object like
[
{"foo":"bar"},
{"foo":"bar"}
]
The code I'm using to allow bulk apis is just a small modification to add option many=True for serializer if the data is a list. It's like:
class FooViewSet(views.ModelViewSet):
def create(self, request, *args, **kwargs):
many = isinstance(request.data, list)
if many:
serializer = self.get_serializer(data=request.data, many=True)
serializer.is_valid(raise_exception=True)
self.perform_bulk_create(serializer)
else:
................
I'm using drf_yasg for api doc generation.
But the problem is the schema generated keep detecting my request body just the single model only. Is there any config to make DRF schema generator knows that it will accept a list type?
Here is the schema which DRF generated
{
"post": {
"operationId": "foos_create",
"description": "",
"parameters": [
{
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/Foo"
}
}
],
"responses": {
"201": {
"description": "",
"schema": {
"$ref": "#/definitions/Foo"
}
}
},
"tags": [
"foos"
]
}
}
My expectation is the schema would be the array type of Foo definition
Any help will be appreciated. Thanks for your time.
I know it very old post but I was facing a similar issue, and as a noob in DRF and python took a while to figure this stuff out.
I just had to add a simple decorator.
FYI I have used https://github.com/miki725/django-rest-framework-bulk for the bulk update.
#method_decorator(name='perform_bulk_create', decorator=swagger_auto_schema(
request_body=ContactSerializer(many=True),
operation_description="post list of contacts"
))

How to integrate Haystack with Django Rest Framework for making GET REST API for searching?

model.py
class Item(models.Model):
name=models.CharField(max_length=50)
company=models.CharField(max_length=100)
search_indexes.py
class ItemIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
name=indexes.CharField(model_attr='name')
company=indexes.CharField(model_attr='company')
def get_model(self):
return Item
def index_queryset(self, using=None):
return self.get_model().objects.all()
serializer.py
class ItemSearchSerializer(serializers.Serializer):
text = serializers.CharField()
name=serializers.CharField()
company=serializers.CharField()
views.py
class ItemSearchViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
serializer_class = ItemSearchSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def get_queryset(self):
request = self.request
queryset = EmptySearchQuerySet()
if request.GET.get('q', ''):
query = request.GET.get('q', '')
queryset =SearchQuerySet().filter(content=query);
return queryset
And in url.py I added :
router.register(r'searchquery', views.ItemSearchViewSet, base_name='searchquery')
Now on making GET request from postman like :
http://127.0.0.1:8000/searchquery/?q=app, I am getting the response as desired as show below.
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"text": "apple\ndjflkj",
"id": 14,
"name": "apple",
"mrp": "45.000000",
"company": "djflkj",
"imageid": "jfhi",
"edible": false,
"discount": "0.000000",
"deliverable": true,
"seller_uid": "ljhkh",
"category": "ldjhgjfdk"
},
{
"text": "app\nhuhiu",
"id": 16,
"name": "app",
"mrp": "78.000000",
"company": "huhiu",
"imageid": "iyiugiy",
"edible": false,
"discount": "45.000000",
"deliverable": true,
"seller_uid": "hjh",
"category": "hhl"
}
]
}
But the reponse time is very slow it takes around 2700 ms everytime ,
and I want to make it fast. As response of elastic search is much fast
but I don't know what I am doing wrong. Not sure but may be due to
these reasons I am getting this delay : 1) Haystack is made for
django, so on integrating it with django rest framework , it may be
getting slow. 2) I am using free Bonsai Elastic search heroku add on
and it has just 125 mb memory.
This is how I am connecting to Bonsai elastic search (setting.py)
ES_URL = urlparse('https://******#pine-1455731.us-east1.bonsaisearch.net')
print ES_URL
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
'URL': ES_URL.scheme + '://' + ES_URL.hostname + ':443',
'INDEX_NAME': 'haystack',
},
}
if ES_URL.username:
HAYSTACK_CONNECTIONS['default']['KWARGS'] = {"http_auth": ES_URL.username + ':' + ES_URL.password}
Any help will be appreciated. I am new to elastic search. I want to do elastic search to search products by name for my android application.
I even don't know whether this is the correct approach to do searching. I thought I would enter name of product I want to search and then i will send a GET request and get all the products which are related.
I did Python Profile please look it here: gist
If any one could suggest me any other way of achieving this I will appreciate your help.
The search response is slow because of this code:
def index_queryset(self, using=None):
return self.get_model().objects.all()
index_queryset is supposed to return query set, you are actually returning all model objects. This method is called for every item which is returned in search.