How to use django groups in order to set group permission - django

I want to use django groups provided in user groups. The django group permissions set in admin panel. Suppose I created two groups teachers and students. I want to set permissions at generic view level. Some views can only be can view or can edit by either student or teacher. These permission were set in django admin as following:
Now I created a createview as following:
class CreateQuestionView(LoginRequiredMixin,generic.CreateView):
model = Question
success_url= reverse_lazy('questions:list')
fields = ['title','description' ]
def form_valid(self,form):
form.instance.user = self.request.user
#self.object.save()
return super().form_valid(form)
Now I want this view to be accessible by only teacher groups. I cant find a proper way to implement group permission.Something like #group_required may work in this case link but cant find any related documentation. What is correct way to implement this?

You want the PermissionRequiredMixin:
class CreateQuestionView(LoginRequiredMixin, PermissionRequiredMixin, generic.CreateView):
permission = "yourapp.perm_code_name"
model = Question
success_url= reverse_lazy('questions:list')
fields = ['title','description' ]
def form_valid(self,form):
form.instance.user = self.request.user
#self.object.save()
return super().form_valid(form)
Note that you do NOT want to test whether your user belongs to a given group (assuming this group has specific perms) - this is an antipattern. All you care about is whether your user has the required perm, however he has it.

Related

django permissions: User should only see and edit "his" objects

I'm building an app to manage flats:
There are different administrations. Each administration has users. Every administrations owns properties and every property has 1 to n flats.
Sofar so good. That's more or less setup.
No comes the tricky part. The users of administration A should only be allowed to see properties and flats that their administration owns.
How would I best do this?
Assuming that there is a ForeingKey relationship between the Administrator model and the User model, you can right a filter on what the user can see. For example:
class UserCreatesAndViewsSomething(LoginRequiredMixin, CreateView):
model = UserLog (or something like that)
template_name = 'some template you have'
fields = ('field you want to display',)
# This function will submit the form to the database
def form_valid(self, form):
# this will help you determine what the current administrator is
administrator = AdministratorModel.objects.get(user=self.request.user, administrator=self.kwargs['administrator'])
form.instance.user = self.request.user
# This will autopopulate administrator input to the current user administrator
form.instance.administrator = administrator
return super(UserCreatesAndViewsSomething, self).form_valid(form)
# get_context_data will help you determine what the user can see.
def get_context_data(self, **kwargs):
administrator = AdministratorModel.objects.get(user=self.request.user, administrator=self.kwargs['administrator'])
context = super(UserCreatesAndViewsSomething, self).get_context_data(**kwargs)
context['something'] = to_journal_entry.objects.filter(user=self.request.user, administrator=administrator)
return context
I know that's a lot, but if you are at all familiar with Django you can certainly do it. You will have to go through some trial and error, but this is the approach I used for my project.
Finale note, this assumes all your users and user inputs are in the same database and the code helps you get only the relevant information. If you are dealing with high importance clients or or some sensitive information you should probably look into multi tenancy, which will set up a different schema or a different database for each of your clients. This will lead a different code structure.
Hope this helps.

Django restrict access to user objects

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)

Using django admin, how can I make sure users can only access objects they own?

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

Django user HiddenInput vs. saving directly in views with Class Based Views

Please this as a consideration question. Maybe somebody will use one of the
solutions below.
I have a couple of models which contain a ForeignKey(User) field.
My class-based create views are derived from the generic CreateView.
There are two options to save the associated user when adding a new object:
Saving the form in the views by overriding the form_valid method;
this doesn't expose user_id (and other not mentioned here data that should not be exposed)
class CreateOfferView(CreateView):
model = Offer
form_class = SomeModelFormWithUserFieldExcluded
def form_valid(self, form):
instance = form.save(commit=False)
instance.user = self.request.user
instance.save()
Saving the form with user id stored (and exposed) in a hidden field.
Here's the tricky part. There are more models with user field... so
when creating a form I need to fill the user field with initial (currently logged in) user and also I need to make that field hidden. For this purpose I've used my OwnFormMixin
class OwnFormMixin(object):
def get_form(self, form_class):
form = super(OwnFormMixin, self).get_form(form_class)
form.fields['user'].widget = forms.HiddenInput()
def get_initial(self):
initial = super(OwnFormMixin, self).get_initial()
initial['user'] = self.request.user.pk
#I could also do this in get_form() with form.fields['user'].initial
class CreateOfferView(OwnFormMixin, CreateView):
model = Offer
form_class = SomeModelFormWithAllFields
There are more CreateXXXView using the OwnFormMixin..
How do you save your user data in the forms?
Hidden vs. saving directly in your views? What are pros/cons?
Unless you're allowing users to modify that ForeignKeyField, there's no reason to include it in a form — I'd go with your first solution of using exclude to keep the user field out of your ModelForm, and setting the user from request.user. In fact, the Django documentation now has an example along these exact lines.
You have the advantage of not having to secure against manipulation of the user_id parameter, not exposing your internal user IDs and not having to worry about the different Create vs. Update cases. A slight disadvantage is that if you ever need the ability to change an object's associated User you'll need to start again.

Django Admin - Validating inlines together with main models

I have quite a complex validation requirement, and I cannot get Django admin to satisfy it.
I have a main model (django.contrib.auth.models.User) and several models which look like
class SomeProfile(models.Model):
user = models.OneToOneField(User)
# more fields
I want to check that, if the user belongs to some group, then it has the corresponding profile. So if user is in group Foo he should have a non empty FooProfile.
Where do I put this validation rule? I cannot put it in the model. Indeed, the user is not created yet when the form is validated, hence I cannot access his groups. So I need to resort to form validation. This is what I put:
class UserAdminForm(forms.ModelForm):
"""
A custom form to add validation rules which cannot live in the
model. We check that users belonging to various groups actually
have the corresponding profiles.
"""
class Meta:
model = User
def clean(self):
# Here is where I would like to put the validation
class FooInline(admin.TabularInline):
model = FooProfile
max_num = 1
class UserAdmin(admin.ModelAdmin):
model = User
form = UserAdminForm
inlines = [FooInline]
admin.site.register(User, UserAdmin)
My problem is that inside UserAdminForm.clean() I do not have access to the data posted inside the inlines. So I can tell whether the user is in group Foo by inspecting self.cleaned_data['groups'], but I have no way to tell whether a FooProfile was transmitted.
How do I check this validation requirement?
Edit:
I try to explain the issue better, because there has been a misunderstading in an answer.
I have an issue when I create a new user. The fact is that the profiles are mandatory (according to the groups). Say an admin creates a new user; then I have to add inlines in the admin form for the various GroupProfiles.
How do I check that the right profiles are not null? I cannot use the clean() method of the User model, because in there I cannot check what groups the user belongs to: it has not been created yet.
I can only access the information about the groups in the clean() method of the form - but there I do not have the information about the profiles, since this information is submitted trhough inlines.
1
well i have been looking around, how all this stuff works, and i found one question very similar here.
2
There are one way to get all the data at the same time maybe with this you can find the answer to your problem
class UserAdminForm(forms.ModelForm):
"""
A custom form to add validation rules which cannot live in the
model. We check that users belonging to various groups actually
have the corresponding profiles.
"""
class Meta:
model = User
def clean(self):
self.data # <--here is all the data of the request
self.data['groups']
self.data['profile_set-0-comments'] # some field
# some validations
return self.cleaned_data