Django: How to Access Installed Third Party App's Model and Data - django

I have been using django-river(https://github.com/javrasya/django-river) application within my app (https://github.com/rupin/WorkflowEngine)
I have used the Django rest framework in my application and would like to get the data from the django-river tables. The name of the table is State.
My serializer is as follows
from river.models import State
from rest_framework import serializers
class StateSerializer(serializers.Serializer):
class Meta:
model = State
fields = ['id', 'label']
Also, my API view is as follows
from rest_framework import generics
from workflowengine.riverserializers.StateSerializer import StateSerializer
from river.models import State
class StateList(generics.ListAPIView):
serializer_class = StateSerializer
def get_queryset(self):
return State.objects.all()
Through the Admin console, I have added 11 states inside my state table, which I have checked with pgadmin.
When i access the API through the browser, I get 11 empty sections in my API ( no error, just the data is missing).
I cant seem to understand how the 11 data points presented in the API are empty. That it presented 11 elements, but no data, which is pretty weird.

I think you need to use:
class StateSerializer(serializers.ModelSerializer):

Related

Django-rules replacement of guardian.mixins.PermissionListMixin

In my django based application I want to enable users to keep track of their locations. Each location has an owner, and the list view should only show the locations the current user owns.
With django-guardian I was able to achieve the same with specifying the following in my views.py:
from django.views import generic
from guardian.mixins import PermissionRequiredMixin, PermissionListMixin
# Create your views here.
from .models import Location
class LocationListView(PermissionListMixin, generic.ListView):
model = Location
permission_required = 'view_location'
paginate_by = 20
ordering = ['name']
How would I create something similar with django-rules?
You need to share Location model so we can advise you properly, in both cases you need to specify queryset either in listview variable or by override get queryset method..
I would suggest you following lines of code assuming location model has foreign key from Auth User model where each location assigned to its owner..
def get queryset(self):
qs = Location.objects.filter(user=self.request.user)
return qs

Enforcing permissions through a related model in Django Rest Framework

I'm working on building out permissions for an API built with Django REST Framework. Let's say I have the following models:
from django.db import models
class Study(models.Model):
pass
class Result(models.Model):
study = models.ForeignKey(Study)
value = models.IntegerField(null=False)
I have basic serializers and views for both of these models. I'll be using per-object permissions to grant users access to one or more studies. I want users to only be able to view Results for a Study which they have permissions to. There are two ways I can think of to do this, and neither seem ideal:
Keep per-object permissions on Results in sync with Study. This is just a non-starter since we want Study to always be the source of truth.
Write a custom permissions class which checks permissions on the related Study when a user tries to access a Result. This actually isn't too bad, but I couldn't find examples of others doing it this way and it got me thinking that I may be thinking about this fundamentally wrong.
Are there existing solutions for this out there? Or is a custom permissions class the way to go? If so, do you have examples of others who've implemented this way?
As you stated, you can make custom permission as per the second way:
And include the permission in your view:
I am considering your study model with some parameter course, based on that i am writing the solution you can consider for any element in study model
models.py
from django.db import models
class Study(models.Model):
course = models.CharField(max_length=50)
class Result(models.Model):
study = models.ForeignKey(Study)
value = models.IntegerField(null=False)
In permission.py
from rest_framework import permissions
class ResultOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS and obj.study.course == request.GET.get('course') :
# read only requests
return True
else:
# other requests such as post, patch, put
return obj.study == request.GET.get('course')
And include ,
class ReviewDetail(viewsets.ViewSet):
permission_classes =[ResultOrReadOnly]
And in urls.py,
Modify it to accept the URL parameter course
I would create a new field called enrolled_users in the Study model to indicate which all user has access to the particular Study object.
from django.db import models
from django.conf import settings
class Study(models.Model):
enrolled_users = models.ManyToManyField(
settings.AUTH_USER_MODEL,
related_name="studies"
)
# other fields
class Result(models.Model):
study = models.ForeignKey(Study)
value = models.IntegerField(null=False)
Then, it will very easy in DRF to filter the queryset in the views
# views.py
from .models import Study, Result
from rest_framework.permissions import IsAuthenticated
class StudyModelViewSet(viewsets.ModelViewSet):
permission_classes = (IsAuthenticated,)
def get_queryset(self):
return Study.objects.filter(enrolled_users=self.request.user)
class ResultModelViewSet(viewsets.ModelViewSet):
permission_classes = (IsAuthenticated,)
def get_queryset(self):
return Result.objects.filter(study__enrolled_users=self.request.user)
Notes
This will handle the object permission (Detail View) request as well
This will not return a status code of HTTP 403, but HTTP 404
Here I used the ModelViewSet class, but, you can use any views, but the filter plays the role.

Same api endpoint for CRUD operations in Django generic apiview

I have been creating different api endpoint for different requests, for eg every single api for get, post, delete and update in generic apiview. But my frontend developer has told me it's a very bad practice and I need to have a single api for all those 4 requests. When I looked it up in the documentation, there is a ListCreateApiView for listing and creating an object, but I cant use it for delete and also for update. How can I include those two in a single endpoint. I don't use modelset view and also functional view. I mostly use generic api views.
Did you try rest framework's ModelViewSet?
i.e.:
from rest_framework.viewsets import ModelViewSet
Which has all the mixins (CRUD) and you can inherit from it in your API view.
Or you can add these mixins based on your requirements:
from rest_framework.mixins import CreateModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin
and you can inherit from each one of them seperately. For instance:
Class SomeView(CreateModelMixin, DestroyModelMixin, GenericViewSet):
pass
which has create and delete ability. You can also use mixins with GenericAPIView:
Class SomeView(CreateModelMixin, DestroyModelMixin, GenericAPIView):
pass
One of the easiest ways to achieve this is by use of Generic views.
Simply start by adding this import to your views.py:
from rest_framework import generics
from .serializers import modelnameSerializer
from .model import modelname
Then here goes your generic class based view.
This view is for listing all items in your model of choice
class yourmodelnameList(generics.ListCreateAPIView):
queryset = modelname.objects.all()
serializer_class = modelnameSerializer
This view is for updating, deleting and retrieving:
class modelnameDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = modelname.objects.all()
serializer_class = modelnameSerializer
In urls.py simply add:
path('yourulchoice/',views.modelnameList.as_view()),
path('yourulchoice/<int:pk>/', views.modelnameDetail.as_view()),
Other things to note:
django rest framework is installed.
ensure rest_framework is added in INSTALLED_APPS in settings.py file.
The two url routes should be able to handle your CRUD needs.

How to access/create a proper Request object for DRF Serializer?

I have created a REST API using DRF, and that works well enough. The frontend is a simple page that allows data to be viewed and updated. Now I am trying to add more interactivity to the site using WebSockets with django-channels. The Channels system is fairly simple to use and also works nicely.
However the issue I am now facing is trying to combine all of the moving pieces to work together. My idea is that the initial page refresh comes through the REST API, and any subsequent updates would automagically come through a WebSocket after every update (with the help of post_save signal). I have nice DRF Serializers for all my models, but alas those do not work without a Request object (for instance HyperLinkedIdentityField):
AssertionError: `HyperlinkedIdentityField` requires the request in the serializer context. Add `context={'request': request}` when instantiating the serializer.
So my question is, how do I somehow create/fake a proper Request object that the Serializers want when trying to serialize my model in a signal handler?
Edit
The more I think about this, the more obvious it becomes that this is not exactly the right way to go. There is no way to craft a single, generic Request object for the serializers, since the model updates which trigger them can come from any source. Thus it would not make sense to even try creating one. I think I have to separate the "base" serializers (without any hyperlinks) and use those to send updates to the clients. Since the hyperlinks won't ever change, I think this is the proper way to go.
In case anyone might be interested, here is how I solved the issue. The main bits and pieces of code are below.
First a simple model (myapp/models.py):
from django.db import models
class MyModel(models.Model):
name = models.TextField()
Then the serializers (myapp/serializers.py):
from rest_framework import serializers
MyModelSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = MyModel
fields = ('url', 'id', 'name')
extra_kwargs = {'url': {'view_name': 'mymodel-detail'}}
MyModelBaseSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('id', 'name')
And the views (myapp/views.py):
from rest_framework import viewsets
from myapp.models import MyModel
from myapp.serializers import MyModelSerializer
class MyModelViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
And finally the Channels message consumer (myapp/consumers.py):
import json
from django.db.models.signals import pre_save
from django.dispatch import receiver
from channels import Group
from myapp.models import MyModel
from myapp.serializers import MyModelBaseSerializer
def ws_add(message):
message.reply_channel.send({"accept": True})
Group("mymodel").add(message.reply_channel)
def ws_disconnect(message):
Group("mymodel").discard(message.reply_channel)
#receiver(post_save, sender=MyModel)
def mymodel_handler(sender, instance, **kwargs):
Group("mymodel").send({
"text": json.dumps({
"model": "mymodel",
"data": MyModelBaseSerializer(instance).data
})
})
I have omitted things like urls.py and routing.py but those are not relevant to the issue. As can be seen, the regular view uses the normal MyModelSerializer which is includes the URL, and then the update handler MyModelBaseSerializer has only fields which are not dependent on any Request object.

Django Proxy Model Permissions Do Not Appear

I extended Django admin site for my app to allow non-staff/superusers access. This is working just fine.
I created a proxy model for an existing model and registered it to my admin site, however, it doesn't appear for non-staff users. From the documentation I read, my understanding is that proxy models get their own permissions. I checked and these don't appear in the list of available permissions.
Here's my code in case it helps:
Normal Model
class Engagement(models.Model):
eng_type = models.CharField(max_length=5)
environment = models.CharField(max_length=8)
is_scoped = models.BooleanField()
class Meta:
ordering = ['eng_type', 'environment']
app_label = 'myapp'
Proxy Model
class NewRequests(Engagement):
class Meta:
proxy = True
app_label = 'myapp'
verbose_name = 'New Request'
verbose_name_plural = 'New Requests'
Model Admin
class NewRequestsAdmin(ModelAdmin):
pass
def queryset(self, request):
return self.model.objects.filter(is_scoped=0)
Custom Admin Registration
myapps_admin_site.register(NewRequests, NewRequestsAdmin)
I've been managing my DB with South. According to this post, you have to tamper with it a bit by following the instructions it points users to. This was a failure. My DB doesn't have a whole lot of info in it, so I uncommented South and ran a regular syncdb to rule out South. Unfortunately, this is still not working and I'm at a loss. Any help is appreciated.
Edit
This was on Django 1.4
Turns out I didn't do anything wrong. I was looking for the permissions under
myapp | New Request | Can add new request
Permissions fall under the parent model.
myapp | engagement | Can add new request
This is fixed in Django 2.2, quoting release notes:
Permissions for proxy models are now created using the content type of the proxy model rather than the content type of the concrete model. A migration will update existing permissions when you run migrate.
and docs:
Proxy models work exactly the same way as concrete models. Permissions are created using the own content type of the proxy model. Proxy models don’t inherit the permissions of the concrete model they subclass.
There is a workaround, you can see it here: https://gist.github.com/magopian/7543724
It can vary based on your django version, but the priciple is the same.
Tested with Django 1.10.1
# -*- coding: utf-8 -*-
"""Add permissions for proxy model.
This is needed because of the bug https://code.djangoproject.com/ticket/11154
in Django (as of 1.6, it's not fixed).
When a permission is created for a proxy model, it actually creates if for it's
base model app_label (eg: for "article" instead of "about", for the About proxy
model).
What we need, however, is that the permission be created for the proxy model
itself, in order to have the proper entries displayed in the admin.
"""
from __future__ import unicode_literals, absolute_import, division
import sys
from django.contrib.auth.management import _get_all_permissions
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import BaseCommand
from django.apps import apps
from django.utils.encoding import smart_text
class Command(BaseCommand):
help = "Fix permissions for proxy models."
def handle(self, *args, **options):
for model in apps.get_models():
opts = model._meta
ctype, created = ContentType.objects.get_or_create(
app_label=opts.app_label,
model=opts.object_name.lower(),
defaults={'name': smart_text(opts.verbose_name_raw)})
for codename, name in _get_all_permissions(opts):
p, created = Permission.objects.get_or_create(
codename=codename,
content_type=ctype,
defaults={'name': name})
if created:
sys.stdout.write('Adding permission {}\n'.format(p))
How to use
create a directory /myproject/myapp/management/commands
create the file /myproject/myapp/management/__init__.py
create the file /myproject/myapp/management/commands/__init__.py
save the code above into /myproject/myapp/management/commands/fix_permissions.py
run /manage.py fix_permissions
This is a known bug in Django: https://code.djangoproject.com/ticket/11154 (check comments for some patches)
As of 2021 and Django 3+, the solution for missing permissions for proxy model is simple, just generate migrations with makemigrations:
app#e31a3ffef22c:~/app$ python manage.py makemigrations my_app
Migrations for 'main':
main/migrations/0193_myproxymodel.py
- Create proxy model MyProxyModel
I came here and wasn't really sure, what is the correct cause/solution to this problem.
For Django 1.11
This issue is related due to the wrong content_type_id in auth_permission table.
By default, it adds the content type of the base model instead of proxy model content type.
I realize this question was closed a while ago, but I'm sharing what worked for me in case it might help others.
It turns out that even though permissions for the proxy models I created were listed under the parent apps (as #chirinosky) has mentioned, and even though I granted my non-super user all permissions, it was still denied access to my proxy models through the admin.
What I had to do was workaround a known Django bug (https://code.djangoproject.com/ticket/11154) and connect to the post_syncdb signal to properly create permissions for the proxy models. The code below is modified from https://djangosnippets.org/snippets/2677/ per some of the comments on that thread.
I placed this in myapp/models.py that held my proxy models. Theoretically this can live in any of your INSTALLED_APPS after django.contrib.contenttypes because it needs to be loaded after the update_contenttypes handler is registered for the post_syncdb signal so we can disconnect it.
def create_proxy_permissions(app, created_models, verbosity, **kwargs):
"""
Creates permissions for proxy models which are not created automatically
by 'django.contrib.auth.management.create_permissions'.
See https://code.djangoproject.com/ticket/11154
Source: https://djangosnippets.org/snippets/2677/
Since we can't rely on 'get_for_model' we must fallback to
'get_by_natural_key'. However, this method doesn't automatically create
missing 'ContentType' so we must ensure all the models' 'ContentType's are
created before running this method. We do so by un-registering the
'update_contenttypes' 'post_syncdb' signal and calling it in here just
before doing everything.
"""
update_contenttypes(app, created_models, verbosity, **kwargs)
app_models = models.get_models(app)
# The permissions we're looking for as (content_type, (codename, name))
searched_perms = list()
# The codenames and ctypes that should exist.
ctypes = set()
for model in app_models:
opts = model._meta
if opts.proxy:
# Can't use 'get_for_model' here since it doesn't return
# the correct 'ContentType' for proxy models.
# See https://code.djangoproject.com/ticket/17648
app_label, model = opts.app_label, opts.object_name.lower()
ctype = ContentType.objects.get_by_natural_key(app_label, model)
ctypes.add(ctype)
for perm in _get_all_permissions(opts, ctype):
searched_perms.append((ctype, perm))
# Find all the Permissions that have a content_type for a model we're
# looking for. We don't need to check for codenames since we already have
# a list of the ones we're going to create.
all_perms = set(Permission.objects.filter(
content_type__in=ctypes,
).values_list(
"content_type", "codename"
))
objs = [
Permission(codename=codename, name=name, content_type=ctype)
for ctype, (codename, name) in searched_perms
if (ctype.pk, codename) not in all_perms
]
Permission.objects.bulk_create(objs)
if verbosity >= 2:
for obj in objs:
sys.stdout.write("Adding permission '%s'" % obj)
models.signals.post_syncdb.connect(create_proxy_permissions)
# See 'create_proxy_permissions' docstring to understand why we un-register
# this signal handler.
models.signals.post_syncdb.disconnect(update_contenttypes)