Django rest-framwork [views]: users can update only modules they created - python

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.

Related

When building different mixions in django, it is not possible to reassign different functionality to the same function

I have a class to delete a user.
There are three mixins (checks).
For authentication
For the fact that the user deletes no other user
That the user is not used
Checks are implemented through mixins
class UserDeleteView(CustomLoginRequiredMixin, CustomUserPassesTestMixin,
CheckedForUseMixin2,
SuccessMessageMixin,
DeleteView):
template_name = 'users/delete.html'
success_url = '/users/'
model = get_user_model()
success_message = \_('User deleted successfully')
class CustomLoginRequiredMixin(LoginRequiredMixin):
login_url = '/login/'
redirect_field_name = 'next'
def handle_no_permission(self):
messages.error(self.request,
_("You are not authorized! Please sign in."))
return super().handle_no_permission()
class CustomUserPassesTestMixin():
def test_func(self):
return self.get_object() == self.request.user
def dispatch(self, request, *args, **kwargs):
if not self.test_func():
messages.error(self.request,
_("You do not have permission to change another "
"user"))
return redirect('list_users')
return super().dispatch(request, *args, **kwargs)
class CheckedForUseMixin2():
def test_func(self):
user = self.get_object()
return not (user.tasks_author.exists() or user.tasks_executor.exists())
def dispatch(self, request, *args, **kwargs):
if not self.test_func():
messages.error(self.request,
_("Can't delete user because it's in use"))
return redirect('list_users')
return super().dispatch(request)
the second time reassigning test_func breaks the test logic.
That is, test_func for some reason always starts returning true or false, regardless of whether the user has tasks.
If I remove test_func, and move the check to an if block, or call the function, for example, test_func1, then everything works.
https://github.com/zhek111/python-project-52
I tried to inherit from different classes (View, Delete View, UserPassesTestMixin and others)
Tried and checked conditions.

Django: validation of restricted Foreign keys forms in Mixins Class views

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

context variables in DRF generic view

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.

updating user profile using django rest framework api

I want to create an API where user can update their profile. In my case, a user can update his/her username and password. To change his/her profile, an API link should be /api/change/usernameOfThatUser. When I use a non-existing username in the link, I still get the userProfileChange API page, and the input boxes are not filled with previous data. How can I solve this?
serializers.py
User = get_user_model()
class UserProfileChangeSerializer(ModelSerializer):
username = CharField(required=False, allow_blank=True, initial="current username")
class Meta:
model = User
fields = [
'username',
'password',
]
def update(self, instance, validated_data):
instance.username = validated_data.get('username',instance.username)
print('instance of username',instance.username)
return instance
views.py
class UserProfileChangeAPIView(UpdateAPIView):
serializer_class = UserProfileChangeSerializer
lookup_field = 'username'
urls.py
url(r'^change/(?P<username>[\w-]+)$', UserProfileChangeAPIView.as_view(), name='changeProfile'),
Maybe try doing something like this instead in your views.py?
from rest_framework import generics, mixins, permissions
User = get_user_model()
class UserIsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.id == request.user.id
class UserProfileChangeAPIView(generics.RetrieveAPIView,
mixins.DestroyModelMixin,
mixins.UpdateModelMixin):
permission_classes = (
permissions.IsAuthenticated,
UserIsOwnerOrReadOnly,
)
serializer_class = UserProfileChangeSerializer
parser_classes = (MultiPartParser, FormParser,)
def get_object(self):
username = self.kwargs["username"]
obj = get_object_or_404(User, username=username)
return obj
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
This will give you all of the existing data for the user based on the username passed in the url. If the username does not exist, it will raise a 404 error. You can also update or delete the object.

Filter django admin by logged in user (Foreign Key showing up)

I want the admin users to see only the model instances they created. I followed these instructions Filter django admin by logged in user
class FilterUserAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if getattr(obj, 'user', None) is None: #Assign user only the first time, superusers can edit without changing user
obj.user = request.user
obj.save()
def queryset(self, request):
qs = super(FilterUserAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(user=request.user)
def has_change_permission(self, request, obj=None):
if not obj:
# the changelist itself
print('query change')
return True # So they can see the change list page
return obj.user == request.user or request.user.is_superuser
class CampaignAdmin(FilterUserAdmin):
...
This is how my code looks like. Everything works fine. I need one more model with campaign as foreign key.
class ScreenAdmin(FilterUserAdmin):
...
admin.site.register(Campaign,CampaignAdmin)
admin.site.register(Screen,ScreenAdmin)
now when I go to screens, and I see campaigns created by other users to select from
I don't want campaigns from other users to be able to be selected
Update: This is my current get_form
class ScreenAdmin(FilterUserAdmin):
list_display = ('screen_name', 'id','screen_URL')
def get_form(self, request, obj=None, **kwargs):
self.exclude = ["beacon"]
if not request.user.is_superuser:
self.exclude.append('user') #here!
form = super(ScreenAdmin, self).get_form(request, obj, **kwargs)
#print(vars(form))
form.base_fields['campaign'].queryset = Campaign.objects.filter(user=request.user)
return form
def changelist_view(self, request, extra_context=None):
if request.user.is_superuser:
self.list_display = ('screen_name','user', 'id','screen_URL')
#print('Change List######')
return super(ScreenAdmin, self).changelist_view(request, extra_context)
this solution is perfectly sane and working
def queryset(self, request):
qs = super(FilterUserAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(user=request.user)
unless you are marked as superuser, an this is probably what you do.
to have access to the admin interface, you must check user as "staff" (is_staff)
if you check "superuser" you will see all of the data, so create other user (for the tests) add him proper rights, but do not mark mim as superuser, only is_staff and test it.

Categories

Resources