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)
Related
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.
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"
))
In Django 2.0 I am using the rest_auth and currently it returns a response like
{
"token": "foo_token",
"user":{
"pk": 1,
"username": "admin",
"email": "test#test.com",
"first_name": "John",
"last_name": "Doe"
}
}
I would like to change this to return something besides the default response django provides. Something like...
{
"token": "foo_token",
"pk":1,
"username": "admin",
"somefield": "Foo Funk"
}
My urls.py look like this currently
url(r'^rest-auth/registration/', include('rest_auth.registration.urls')),
url(r'^refresh-token/', refresh_jwt_token),
url(r'^api/userlist', users.user_list),
The only place I can find to possibly change the response is in library files which I am sure is not wise to change. Any help would be great.
rest_auth allows you to change the responses by specifying your own serializer implementation in your settings.py.
For example if you wanted to customize the response for JWT authentication, then you might create:
# myapp/serializers.py
class MyCustomJWTSerializer(serializers.Serializer):
token = serializers.CharField()
pk = serializers.IntegerField()
username = serializers.CharField()
...
which you can then configure in your settings.py as:
REST_AUTH_SERIALIZERS = {
'JWT_SERIALIZER': 'myapp.serializers.MyCustomJWTSerializer'
}
More info here: https://django-rest-auth.readthedocs.io/en/latest/configuration.html
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.
I'm using the Django Rest Framework I noticed on the web browseable part of the API there is a button called 'options' when clicked it shows the following...
HTTP 200 OK Vary: Accept Content-Type: text/html Allow: HEAD, GET, OPTIONS
{
"parses": [
"application/json",
"application/x-www-form-urlencoded",
"multipart/form-data"
],
"renders": [
"application/json",
"text/html"
],
"name": "Products",
"description": "API endpoint."
}
my question is, is there anyway I could list out here all the filter options an other stuff for this url?
You can make OPTIONS return whatever you want, by overriding the .metadata() method on the view.
See here: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/views.py#L340
Update as of 2015: We now have a customizable metadata API that makes this easier: http://www.django-rest-framework.org/api-guide/metadata/
You can totally do this. Here's a custom metadata class that I've been keeping up to date here on StackOverflow. This simply lists all the available filters, their types, and their choices. It also lists the ordering fields that are available on a class:
class SimpleMetadataWithFilters(SimpleMetadata):
def determine_metadata(self, request, view):
metadata = super(SimpleMetadataWithFilters, self).determine_metadata(request, view)
filters = OrderedDict()
if not hasattr(view, 'filter_class'):
# This is the API Root, which is not filtered.
return metadata
for filter_name, filter_type in view.filter_class.base_filters.items():
filter_parts = filter_name.split('__')
filter_name = filter_parts[0]
attrs = OrderedDict()
# Type
attrs['type'] = filter_type.__class__.__name__
# Lookup fields
if len(filter_parts) > 1:
# Has a lookup type (__gt, __lt, etc.)
lookup_type = filter_parts[1]
if filters.get(filter_name) is not None:
# We've done a filter with this name previously, just
# append the value.
attrs['lookup_types'] = filters[filter_name]['lookup_types']
attrs['lookup_types'].append(lookup_type)
else:
attrs['lookup_types'] = [lookup_type]
else:
# Exact match or RelatedFilter
if isinstance(filter_type, RelatedFilter):
model_name = (filter_type.filterset.Meta.model.
_meta.verbose_name_plural.title())
attrs['lookup_types'] = "See available filters for '%s'" % \
model_name
else:
attrs['lookup_types'] = ['exact']
# Do choices
choices = filter_type.extra.get('choices', False)
if choices:
attrs['choices'] = [
{
'value': choice_value,
'display_name': force_text(choice_name, strings_only=True)
}
for choice_value, choice_name in choices
]
# Wrap up.
filters[filter_name] = attrs
metadata['filters'] = filters
if hasattr(view, 'ordering_fields'):
metadata['ordering'] = view.ordering_fields
return metadata
Put that somewhere in your project, then set your DEFAULT_METADATA_CLASS, and you should be all set, with a new key on your OPTIONS requests like so:
"filters": {
"sub_opinions": {
"type": "RelatedFilter"
},
"source": {
"type": "MultipleChoiceFilter",
"choices": [
{
"display_name": "court website",
"value": "C"
},
]
}
...more...
}
This will also display choices, mirroring the way it's handled elsewhere in DRF.