I have Folder model. Folder has foreignkey to User called folders.
In django rest framework I would like to create new Folder object. How do I specify "create object at self.request.user.folders"? under current (out of the box vanilla ModelViewSet) implementation I get error:
folder.folder.user_id may not be NULL
This indicates that a we are trying to create Folder object is without specifying a user.
Is there a built in way or will I need to override the create method and pass user in to the serializer as a argument?
Note that we don't want to pass user_id in request.DATA due to security issues.
models.py:
class FolderModel(Model):
user = ForeignKey(User, related_name='folders')
title = CharField(max_length=100)
views.py:
class FolderView(viewsets.ModelViewSet):
serializer_class = serializers.FoldereSerializer
model = FolderModel
def get_queryset(self):
return self.request.user.folders.all()
The original answer no longer works as of DRF 3.0, as the functions have changed. The relevant doc is here.
From the docs:
The pre_save and post_save hooks no longer exist, but are replaced with perform_create(self, serializer) and perform_update(self, serializer).
These methods should save the object instance by calling serializer.save(), adding in any additional arguments as required. They may also perform any custom pre-save or post-save behavior.
For example:
def perform_create(self, serializer):
# Include the owner attribute directly, rather than from request data.
instance = serializer.save(owner=self.request.user)
# Perform a custom post-save action.
send_email(instance.to_email, instance.message)
fom the docs`:
def pre_save(self, obj):
obj.owner = self.request.user
Related
I have Node and User models which both belong to an Organisation. I want to ensure that a User will only ever see Node instances belonging to their Organisation.
For this I want to override the Node objects Manager with one that returns a query_set of User owned filtered results.
Based on https://docs.djangoproject.com/en/2.1/topics/db/managers/#modifying-a-manager-s-initial-queryset
the relevant models.py code I have is below:
class Organisation(models.Model):
users = models.ManyToManyField(User, related_name='organisation')
...
class UserNodeManager(models.Manager):
def get_queryset(self, request):
return super().get_queryset().filter(organisation=self.request.user.organisation.first())
class Node(models.Model):
organisation = models.ForeignKey(
Organisation, related_name='nodes', on_delete=models.CASCADE)
uuid = models.UUIDField(primary_key=True, verbose_name="UUID")
...
objects = UserNodeManager
views.py
class NodeListView(LoginRequiredMixin, generic.ListView):
model = Node
EDIT
I can add custom query_set to individual views and this does work as below:
views.py
class NodeListView(LoginRequiredMixin, generic.ListView):
model = Node
def get_queryset(self):
return Node.objects.filter(organisation__users__id=self.request.user.pk)
However, my intention is to be DRY and override a 'master' query_set method at a single point so that any view (e.g. form dropdown list, API endpoint) will perform the user restricted query without additional code.
For example, I am using django's generic list views have a form for adding a Scan object which requires a user to select a Node the Scan belongs to. The form currently shows Nodes from other Organisations, which is against the permissions logic I need.
Unfortunately, the overridden Node.objects property does not seem to have any effect and any User can see all Nodes. Am I taking the right approach?
I think the problem is here:
objects = UserNodeManager
You need to initiate UserNodeManager instance like this:
objects = UserNodeManager()
Also, it should throw error when you calling YourModel.objects.all() method(which is called from get_queryset method in view), because when it calls get_queryset() method, it does not pass request. So I think it would be a better approach:
class UserNodeManager(models.Manager):
def all(self, request=None):
qs = super(UserNodeManager, self).all()
if request:
return qs.filter(...)
return qs
Or you can create a new manager method like this(optional):
class UserNodeManager(models.Manager):
def user_specific_nodes(self, request):
return self.get_queryset().filter(...)
Also update in the view:
class NodeListView(LoginRequiredMixin, generic.ListView):
model = Node
def get_queryset(self):
return Node.objects.all(self.request) # where you can obviously use filter(...) or Model.objects.user_specific_nodes(self.request)
Update
from comments
Thing is that, you need to pass request with filter() or all(). In Generic views, the get_queryset method does not pass that information to all(). So you need to pass that either way. There is another way, to use a middleware like this django-crequest. You can use it like this:
from crequest.middleware import CrequestMiddleware
class UserNodeManager(models.Manager):
def all(self):
qs = super(UserNodeManager, self).all()
request = CrequestMiddleware.get_request()
return qs.filter(...)
The best way of achieving this is by using groups and custom permissions. You might add a group for every organization and set the correct permissions for those groups over your Nodes.
Take a look to this article, it might help: User Groups with Custom Permissions in Django
#ruddra thanks again for your guidance.
While your middleware example did not have effect for me (as user could still see others' objects), I was able to use that with the django documentation to finally implement the Manager similar to:
class UserDeviceManager(models.Manager):
def get_queryset(self):
request = CrequestMiddleware.get_request()
return super().get_queryset().filter(organisation=request.user.organisation)
I currently have the following viewset:
class ArtistProfileViewSet(viewsets.ModelViewSet):
queryset = ArtistProfile.objects.all()
serializer_class = ArtistProfileSerializer
def perform_create(self, serializer):
if serializer.is_valid():
serializer.save()
With the following serializers:
class SimpleArtistTrackSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ArtistTrack
fields = (...my fields...)
class ArtistProfileSerializer(serializers.HyperlinkedModelSerializer):
owners = UserSerializer(many=True, required=False)
tracks = SimpleArtistTrackSerializer(many=True, required=False)
class Meta:
model = ArtistProfile
fields = (...my fields...)
I am getting the following error:
AssertionError: You cannot call `.save()` after accessing `serializer.data`.If you need to access data before committing to the database then inspect 'serializer.validated_data' instead.
I don't see where I would be editing the serializer.data object. Is there something I am missing that would cause this edit? How can I resolve this error?
If you are using PyCharm and you were debugging some code, check if you hadn't called serializer.data in your debugger "watches". It call .data everytime your debugger stop in given context(breakpoint) that given serializer appear, even if you don't call it explicit(serializer.data) in your code.
You don't need to call is_valid there at all. When you do an update the serializer is initialized with the model instance (based of the pk in the route, e.g PATCH /artists/{3}/). You would call is_valid() if you are passing data to the serializer as in:
ser = MySerializer(data=request.data) # validate incoming post, etc
ser.is_valid(raise_exceptions=True)
data = ser.validated_data
To send additional data to the serializer when saving (e.g. to set a company, or set a user, etc), use an override like this:
def perform_create(self, serializer):
serializer.save(company=self.request.user.company)
For more details, I would browse the DRF source code and see what it is doing. it's very well written and clear.
Your comment indicates another question which is much bigger. You say you want to add additional data to the "join" table between artists and user. This can be done, but is a whole different topic and you end up manually managing the relationship. Also, read up on nested writable serializers, it's a big topic and even more manual labor.
In the same way that "watches" if you are debugging, try not to inspect (or expand) the 'data' field of the serializer before going through the save() line.
If the queryset exists, I would like to update the existing instance instead of creating a brand new one.
The way I currently have it raises an error saying unexpected kwargs are being passed if the serializer is trying to update.
Could someone please help me do this?
class APNSDeviceViewSet(DeviceViewSetMixin, ModelViewSet):
queryset = APNSDevice.objects.all()
serializer_class = APNSDeviceSerializer
def perform_create(self, serializer):
queryset = APNSDevice.objects.filter(user=self.request.user)
if queryset.exists():
# update serializer to contain new information
serializer.update(
device_type=self.request.data.get('device_type'),
registration_id=self.request.data.get('registration_id'),
device_id=self.request.data.get('device_id'))
else:
# create a new model instance
serializer.save(
user=self.request.user,
device_type=self.request.data.get('device_type'),
registration_id=self.request.data.get('registration_id'),
device_id=self.request.data.get('device_id'))
Thank you!
Peering into the Django Rest Framework code, specifically serializers.py, it's clear you're providing incorrect parameters for the update() method. Its signature is not like the save() method's signature. It expects as a first argument the model instance, and as a second argument a dictionary containing the data. So in your case it would look something like this:
instance = queryset[0]
serializer.update(instance, {'device_type': self.request.data.get('device_type'), 'registration_id': self.request.data.get('registration_id'), 'device_id': self.request.data.get('device_id')})
All you want is a base queryset that is restricted to the current user.
This is performed by the filters in an elegant and simple way.
On a side note you should not invoke serializer.update by yourself. It's called by the serializer through the serializer.save method. The difference between update et create is that the serializer is instantiated with a Model's instance for updates.
I'm trying to build a selfservice website using django admin. Say a user shall only be able to edit his own data. I can make sure that he can only retrieve his own records this way:
# admin.py
class PersonalDataAdmin(admin.ModelAdmin):
model = PersonalData
exclude = ('data_confirmed',)
list_display = ('first_name', 'last_name', 'email')
def get_queryset(self, request):
qs = super(PersonalDataAdmin, self).get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(user=request.user)
What about saving though? In order for the View to show up in the admin interface, the user need rights to change entries of PersonalData. How can I check when receiving the POST request, that the object belong to the user?
I think I need implement a ModelForm for this:
class PersonDataForm(ModelForm):
pass
and add it to PersonalDataAdmin. Then I could overwrite the clean() or save() method. Is this the right way to go? Also for this case where there is only one record per user is it possible to skip the change list view and link directly to the change view?
I would go with overriding
ModelAdmin.has_change_permission(request, obj=None)
where you can change request.user versus the object. Also see related ModelAdmin.has_*_permission() methods.
For the restring the viewing of objects, check:
View permissions in Django
I am trying to define custom logic to be executed when POSTing/creating a new API object but i'm receiving error: "<Appointment: apitest>" needs to have a value for field "appointment" before this many-to-many relationship can be used.
class AppointmentViewSet(viewsets.ModelViewSet):
queryset = Appointment.objects.all()
serializer_class = AppointmentSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
def create(self, request):
serializer = AppointmentSerializer(data=request.DATA)
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
def pre_save(self, obj):
obj.service_recipient = self.request.user
You are using Django REST Framework 2.x, which has very limited support for related object creation. Right now, you are trying to create a reverse-relationship (most likely through a foreign key or one-to-one), which Django REST Framework 2.x does not actually support.
Django REST Framework 3.0 fixes a lot of the issues that we previously had with related object creation by making it so now it has to be manually implemented. You can find a lot more information about the changes in the release announcement, and determine how it must be implemented in your case.