I'm trying to build some frontend stuff in DRF - react. someAPI should run a query against db using current logged user id as parameter. After some hours trying here and there, following code works, but Im not sure is the right way to do it as involves using mixins and function overriding.
Question is, How can I achieve same result using generic DRF views?
class someAPI(mixins.ListModelMixin,
generics.GenericAPIView):
serializer_class = someSerializer
def get(self, request, *args, **kwargs):
customRole = get_object_or_404(Role, user=request.user)
self.queryset = ClassDependingOnRole.objects.filter(role=customRole.id)
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
You can use ListCreateAPIView directly.It is more useful.
class SomeApi(generics.ListCreateAPIView):
serializer_class = SomeSerializer
def get_queryset(self):
role=get_object_404(Role, user=self.request.user)
return ClassDependingOnRole.objects.filter(role=role)
It generates post and get methods automatically and validates serializer validations.You da find more detail from here about ListCreateAPIView.
Related
So I have a view for my Django app that is used to update/delete user profiles. I'm wondering is there a better way to check if the person who's requesting the change is the same one that's in the url.
request url looks like this /profile/<str:username>
class UserDetail(RetrieveUpdateDestroyAPIView):
queryset = User.objects.all()
serializer_class = UserSerializerFull
permission_classes = [IsAuthenticated]
lookup_field = "username"
def put(self, request, *args, **kwargs):
if str(request.user) == kwargs.get("username"):
return super().put(request, *args, **kwargs)
return Response(
data={"msg": "unauthorized request"}, status=status.HTTP_403_FORBIDDEN
)
def patch(self, request, *args, **kwargs):
if str(request.user) == kwargs.get("username"):
return super().patch(request, *args, **kwargs)
return Response(
data={"msg": "unauthorized request"}, status=status.HTTP_403_FORBIDDEN
)
def delete(self, request, *args, **kwargs):
if str(request.user) == kwargs.get("username"):
return super().delete(request, *args, **kwargs)
return Response(
data={"msg": "unauthorized request"}, status=status.HTTP_403_FORBIDDEN
)
As all of the methods are repeating the same code is there a way to write something just for once and use it everywhere.
Also for other models that are made by the User profile i have the same issue as a lot of the code is repeating itself.
You can implement a custom permission class as follows:
from rest_framework import permissions
class IsRequestingUser(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# Write permissions are only allowed to the owner of the snippet.
return obj.user == request.user
Use this permission class in your class:
class UserDetail(RetrieveUpdateDestroyAPIView):
# other logic
permission_classes = [IsAuthenticated, IsRequestingUser]
# other logic
For more details, you can see the here.
That's what permissions are for.
class IsOwner(IsAuthenticated):
def has_object_permission(self, request, view, obj):
return obj.user == request.user
Something like that will do the trick but you will have to adjust it for your needs.
I am a beginner to django rest-framework and trying to create new record using POST method in ListAPIView.
Here's my serializer:
from scheme.models import ProjectScheme, ProjectSchemeMaster
from rest_framework import serializers
class SchemeDetailSerializer(serializers.ModelSerializer):
class Meta:
model = ProjectScheme
fields = ('id', 'name', 'parent_scheme_id', 'rule', 'created_on', 'created_by', 'updated_on','updated_by')
depth=1
And view:
class ProjectSchemeList(ListAPIView):
"""
List all Schemes
"""
serializer_class = SchemeDetailSerializer
# pagination_class = ProjectLimitOffsetPagination
def get_queryset(self, *args, **kwargs):
comp_logger.info('invoked scheme list all')
schemes = ProjectScheme.objects.all().order_by('-id')
return schemes
def post(self, request, *args, **kwargs):
if serializer_class.is_valid():
serializer_class.save()
return Response(serializer_class.data, status=status.HTTP_201_CREATED)
return Response(serializer_class.errors, status=status.HTTP_400_BAD_REQUEST)
I get this error:
NameError at /scheme/schemes/
name 'serializer_class' is not defined
How do I pass request data to serializer_class?
Created functioanlity is included by default in CreateAPIView generic view, or if you want to provide list and create functionality, you can use ListCreateAPIView which provides both. More details on DRF's generic views here.
class ProjectSchemeList(ListCreateAPIView):
serializer_class = SchemeDetailSerializer
def get_queryset(self, *args, **kwargs):
comp_logger.info('invoked scheme list all')
schemes = ProjectScheme.objects.all().order_by('-id')
return schemes
With this definition, you won't need to manually write a post method.
If you want to manually define a post methdod, you can investiage how it is written in generic CreateAPIView and copy it, it's slighly different from how you want to write it. Finally, following is your version of the post method with errors fixed:
class ProjectSchemeList(ListAPIView):
serializer_class = SchemeDetailSerializer
def get_queryset(self, *args, **kwargs):
comp_logger.info('invoked scheme list all')
schemes = ProjectScheme.objects.all().order_by('-id')
return schemes
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Notice how we use self.serializer_class(data=request.data) instead of just serializer_class
Context: how I handled foreign keys restrictions on GET
I have some trouble validating this form:
class RecordConsultationForm(forms.ModelForm):
class Meta:
model = Consultation
fields = ["fk_type", "fk_client", "duration", "date"]
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(RecordConsultationForm, self).__init__(*args, **kwargs)
self.fields['fk_client'].queryset = Client.objects.filter(fk_owner=self.user) # <=====HERE
The queryset restricts the available clients to users. Pretty effective, I just had to add the following to get_context_data():
#method_decorator(login_required, name='dispatch')
class BrowseConsultations(BrowseAndCreate):
template_name = "console/browse_consultations.html"
model = Consultation
form_class = RecordConsultationForm
success_url = 'browse_consultations'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = self.form_class(user = self.request.user) #<=====HERE
return context
def post(self, request):
form = self.form_class(user = self.request.user) #<=====HERE
return super().post(request)
Form validation on BrowseConsultations
Despite what I added in get_context_data() and post(), form data on POST does not seem valid, as if user were None (so it seems):
Invalid fk_client: Select a valid choice. 2 is not one of the available choices.
Maybe get_context_data() was not the proper place to set the user? Is there a way I could tweak the form in post()? Is it the proper way to do it?
Additional details
BrowseAndCreate BrowseConsultations inherits from is a Mixing class I created to handle common ordering tasks and messages. Here is a portion of it:
#method_decorator(login_required, name='dispatch')
class BrowseAndCreate(CreateView, TemplateView):
"""display a bunch of items from a model and the form to create new instances"""
def post(self, request):
super().post(request)
return redirect(self.success_url)
def form_valid(self, form):
super().form_valid(form)
messages.add_message(self.request, messages.SUCCESS,
"Recorded in {}".format(self.object.status))
def form_invalid(self, form):
for e in form.errors.items():
messages.add_message(self.request, messages.WARNING,
"Invalid {}: {}".format(e[0], e[1][0]))
Environment
django 3.0.4
python 3.7
First of all, the CreateView (that your BrowseAndCreate inherits from) handles form validation in the post method, where it calls form_valid on success and form_invalid on failure. Both these methods should return an HTTP response.
Furthermore, the get_context_data from FormMixin that you are overriding already takes care of getting the form data.
If you need the user in your form, you could have this in your form:
class RecordConsultationForm(forms.Form):
def __init__(self, user, *args, **kwargs):
self.user = user
super().__init__(*args, **kwargs)
And this in your view:
class BrowseConsultations(BrowseAndCreate):
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
I have a Django rest framework api set up, and I'm trying to insert the current time into incoming PUT requests. I currently have:
class ItemViewSet(viewsets.ModelViewSet):
queryset = Item.objects.filter(done = False).order_by('-time')
serializer_class = ItemSerializer
paginate_by = None
def list(self, request, *args, **kwargs):
self.object_list = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(self.object_list, many=True)
return Response({'results': serializer.data})
This handles partial updates, but I would like to be able to send a request setting an Item to done = True and have the api also insert a unix timestamp into the data sent to the serializer. Could I alter the request object like this, or is there a better way?
def put(self, request, *args, **kwargs):
request.data['time'] = time.time()
return self.partial_update(request, *args, **kwargs)
Instead of modifying request, override serializer's method update.
Class ItemlSerializer(serializers.ModelSerializer):
class Meta:
model = ItemModel
fields = '__all__'
read_only_fields = ('time',)
def update(self, instance, validated_data):
instance.time = time.time()
return super().update(instance, validated_data)
You make a Parent serializer mixin with a serializer method field. Then all your serializers can inherit this serializer mixin.
class TimeStampSerializerMixin(object):
timestamp = serializers.SerializerMethodField()
def get_timestamp((self, obj):
return str(timezone.now())
In order to use UpdateView without pk in the url, I override the get_object function, however, when I test it, the form is not saved.
There are two models involved: user and Profile, with a OnetoOne relation.
My url is as following:
url(r'^profile/edit/$', profileviews.ProfileUpdateView.as_view(template_name="Pages_Profile/profileupdate.html"),name="profileupdate_URL"),
And the view class is:
class ProfileUpdateView(UpdateView):
model = Profile
form_class = UserProfileForm
def get_object(self, queryset=None):
return get_object_or_404(self.model, user=self.request.user)
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super(ProfileUpdateView, self).dispatch(request, *args, **kwargs)
I think the problem lays on the view, but I can't figure out.
Can anyone help me out?
Thanks.
There is a project called django-braces which includes a whole bunch of very useful generic view mixins to help with using Django CBV (class based views):
from braces.views import LoginRequiredMixin
class ProfileUpdateView(LoginRequiredMixin, UpdateView):
model = Profile
form_class = UserProfileForm
def get_object(self, queryset=None):
return get_object_or_404(self.model, user=self.request.user)
You can find more about it here.