bennylope / django-organizations

:couple: Multi-user accounts for Django projects
http://django-organizations.readthedocs.org/
BSD 2-Clause "Simplified" License
1.31k stars 212 forks source link

DRF Integration #210

Open StanGirard opened 3 years ago

StanGirard commented 3 years ago

ANSWER ON THIRD COMMENT

Hello guys,

Thanks a lot for this outstanding work.

I'm trying to integrate dj-organization into my DRF project and I'm having a bit of an issue. I'm struggling to filter the results the view sends back based on the user organization. I'm fairly new to Django and I'm having difficulties integrating it with DRF.

Here is my code.

My Organization

from django.db import models
from organizations.models import Organization

# Create your models here.

class Website(Organization):
    url = models.CharField(max_length=200)

My Model which references the org

import datetime
from django.db import models
from django.utils import timezone
from org.models import Website

class Extractor(models.Model):
    Org = models.ForeignKey(Website, related_name='extractor', on_delete=models.CASCADE)
    extractor_type = models.TextChoices('Extractor', 'HEADERS IMAGES LINKS')
    url = models.CharField(max_length=200)
    result = models.JSONField(blank=True, null=True)
    type_audit = models.CharField(blank=True, choices=extractor_type.choices, max_length=20)
    task_id = models.CharField(max_length=50, blank=True, null=True)
    status_job = models.CharField(max_length=30, blank=True, null=True)
    begin_date = models.DateTimeField(blank=True, null=True)

    def __repr__(self):
        return '<Audit {}>'.format(self.url)

And my view for the Extractor Model

Extractor view

class ExtractorViewSet(viewsets.ModelViewSet):
    permission_classes = [permissions.IsAuthenticated]
    """
    API endpoint that allows users to be viewed or edited.
    """
    queryset = Extractor.objects.order_by('-begin_date')
    serializer_class = ExtractorSerializer
    filter_backends = [DjangoFilterBackend,filters.OrderingFilter]
    filterset_fields = ['type_audit', 'status_job']
    ordering_fields = ['id', 'type_audit', 'begin_date']

My question is as follow:

Where do I filter the Extractor object based on the user in the request.user so that he only sees the extractor for his organization ?

I know this isn't an Issue as per say, but I'll be interested to add the answer in your documentation and an How To for others :)

Thanks a lot. Have a great day

StanGirard commented 3 years ago

I've looked into old issues to try to find an answer. I've compiled an answer but now I get a 404 on my route when I add this change: I've added the Mixins Organization and MembershipRequired and changed the get_queryset method.

Not Found: /api/extractor/
[21/Dec/2020 12:49:06] "GET /api/extractor/?ordering=id&page=1&page_size=10 HTTP/1.1" 404 1797

Extractor View

from extractor.models import Extractor
from rest_framework import viewsets
from rest_framework import filters
from rest_framework import permissions
from django_filters.rest_framework import DjangoFilterBackend

from extractor.serializers import ExtractorSerializer
from rest_framework import permissions
from org.models import Website
from organizations.views.mixins import MembershipRequiredMixin, OrganizationMixin

class ExtractorViewSet(MembershipRequiredMixin, OrganizationMixin,viewsets.ModelViewSet):
    permission_classes = [permissions.IsAuthenticated]
    """
    API endpoint that allows users to be viewed or edited.
    """

    queryset = Extractor.objects.order_by('-begin_date')
    serializer_class = ExtractorSerializer
    filter_backends = [DjangoFilterBackend,filters.OrderingFilter]
    filterset_fields = ['type_audit', 'status_job']
    ordering_fields = ['id', 'type_audit', 'begin_date']

    def get_queryset(self):
        return self.queryset.filter(org=self.get_organization())

I'm pretty sure there is something that I don't understand, and I'm probably a few lines of code away from the answer ๐Ÿ˜„

StanGirard commented 3 years ago

Answer

For people trying to integrate Django Organizations with Django Rest Framework (DRF) here is a working implementation of filtering results based on the user in the request.

Organization model

from django.db import models
from organizations.models import Organization

# Create your models here.

class Website(Organization):
    url = models.CharField(max_length=200)

Models

I added a custom manager for the for_user method

import datetime
from django.db import models
from django.utils import timezone
from org.models import Website

class ForUser(models.Manager):
    def for_user(self, user):
        org = Website.objects.filter(users=user)
        return self.get_queryset().filter(org__in=org.values_list('id', flat=True))
class Extractor(models.Model):
    Org = models.ForeignKey(Website, related_name='extractor', on_delete=models.CASCADE)
    extractor_type = models.TextChoices('Extractor', 'HEADERS IMAGES LINKS')
    url = models.CharField(max_length=200)
    result = models.JSONField(blank=True, null=True)
    type_audit = models.CharField(blank=True, choices=extractor_type.choices, max_length=20)
    task_id = models.CharField(max_length=50, blank=True, null=True)
    status_job = models.CharField(max_length=30, blank=True, null=True)
    begin_date = models.DateTimeField(blank=True, null=True)

    objects = ForUser()

    def __repr__(self):
        return '<Audit {}>'.format(self.url)

View

And then I overloaded the get_queryset method in my view.

from extractor.models import Extractor
from rest_framework import viewsets
from rest_framework import filters
from rest_framework import permissions
from django_filters.rest_framework import DjangoFilterBackend
from extractor.serializers import ExtractorSerializer
from rest_framework import permissions
from org.models import Website

class ExtractorViewSet(viewsets.ModelViewSet):
    permission_classes = [permissions.IsAuthenticated]
    """
    API endpoint that allows users to be viewed or edited.
    """
    serializer_class = ExtractorSerializer
    filter_backends = [DjangoFilterBackend,filters.OrderingFilter]
    filterset_fields = ['type_audit', 'status_job']
    ordering_fields = ['id', 'type_audit', 'begin_date']

    def get_queryset(self):
        return Extractor.objects.for_user(self.request.user).order_by('-begin_date')

I hope this will help somebody ๐Ÿ˜„ It isn't very hard but this was my first time using django.

Thanks @bennylope for this awesome project.

StanGirard commented 3 years ago

Please let me know if you have a better solution ๐Ÿ˜„

obvionaoe commented 2 years ago

Hi @bennylope , is there any way to integrate this, sort of out of the box with DRF? As I'm pretty happy with the way this package handles everything already, and it'll be a bit of a hassle to "redo" most of the logic to integrate with DRF.

marqpdx commented 2 years ago

i think a way to go about this is to build a DRF layer that simple is annexed to the current code, meaning there's no need to change current code at all.

For example, one could (i'm already thinking in this direction), start with one view, say displaying one's groups after authentication, and create a DRF endpoint for that. One would likely need to extend the existing view for this functionality, run that view through a serializer, codify and endpoint, add in the DRF code... none of which seems hard, just a bit methodical and time-consuming. All the DEF endpoints could extend existing views and/or just re-use some of the view code in a DRF way.

I may try this, and will post back if it's bears fruit.

obvionaoe commented 2 years ago

@marqpdx Seems like a good idea!

obvionaoe commented 2 years ago

Any updates @marqpdx ?