Protecting user's model view from other users - django

I'm trying to write a generic DetailView for my model (which is related to User), but I don't know how to limit access for specific model (view) for only that User (owner). I've looked into docs but haven't found any guide how to do that the proper way. So far I've managed to overwrite test_func() method of the class using UserPassesTestMixin where i check if object.user == self.request.user but I'm not sure if that's the proper way to do that.
What I've just said may not be clear, so e.g.
Model A of id 4 is related (owned by) User A.
Now, if User B tries to request Model A DetailView (/models/4) he should be rejected with 403 Error Code, or even better, custom error page.

Have you tried a query like this?
MyTable.objects.filter(user=self.request.user)
This only returns the objects related to the current user.

There are few ways you can try
verify user permissions in your own views using the has_perm method provided in the user model.
if user.has_perm('foo.add_bar'):
return HttpResponse("You are authorized to add content!")
else:
return HttpResponse("Permission to add denied")
verify in your templates using the perms variable that is automatically added to the template context.
{% if perms.app_label.can_do_something %}
This content will be shown users with can_do_something permission.
{% endif %}
This content will be shown to all users.
You can also create your own permissions to the models
class SomeModel(models.Model):
owner = models.ForeignKey(User)
content = models.TextField()
class Meta:
permissions = (
('view_content', 'View content'),
)
have a look at this link it will give you an idea.

Related

Adding a new ManyToMany relationship in a form?

Django newbie here. I keep encountering the exact same design paradigm, which seems like it should be common for everyone, yet can't find out how it's supposed to be resolved.
Picture the following ManyToMany relationships:
An organization could have many members; each person could be a member of many organizations
An organization could manage many objects. An object could be in use by multiple organizations. The same applies to the relationship between people and objects.
An organization, person, or object could have multiple media elements (photos, videos, etc) of it, and a single media element could be tagged with numerous organizations, people, or objects
Nothing unusual. But how does a site user add a new person, organization, or object? It seems that if someone is filling out an "add an organization" form, in addition to choosing from existing people, objects, media, etc there should be a button for "new member", "new object", "new photo", etc, and this should take you to the forms for creating new members, objects, media, etc. And when they're done, it should go back to the previous page - whose form filled-out form entries should persist, and the newly created entry should be listed in its respective ManyToMany field.
The problem is, I don't know how to do this. I don't know how one would add a button in the middle of a form, and can't seem to find anything to clarify how to do it. I assume it would need to be a submit button, with a different name / id or some other way so that views.py can treat it differently, via flagging an "incomplete" record in the database. And the new form will need to be passed information about what page it needs to go back to when it's submitted.
Am I thinking about this correctly? If so, then I think the only knowledge I lack is how to add a second submit button in a form and how to recognize its usage in views.py.
If I'm not thinking about this correctly, however, please suggest an alternative paradigm that you think makes more sense :) This is my first Django project, so I'm learning as I do it.
ED: I'm thinking maybe instead of using {{ form.as_p }} to display it, I need to iterate over fields and use some logic to add the extra submit button in the middle as html: What's the best way to add custom HTML in the middle of a form with many fields?
Then I'll just need to figure out a way to detect which submit button was used and put some logic behind it to handle partially-submitted forms, redirecting to a form to create the relation, and then redirecting back on submit... I can probably figure this out...
The first thing I would recommend is to define your models. Lay them all out with the attributes you require. That'll be the foundation for everything else you want to accomplish. You can do everything you mentioned with Django... it's just a matter of coding it. As far as I know you would need to create each model instance separately, and then you can refer to already created instances in the create form for the Organization model for example. I would look into the docs for generic views that help you create objects easily. Then you can link to other create forms if you wish. I don't know how you can create multiple instances of different models in one form, and I don't think it would be the best way to do things even if you can. Here's an example of a model, a create form, a create view, and corresponding url:
# models.py
class Organization(models.Model):
name = models.CharField(max_length=100, null=True, blank=True)
# forms.py
class OrganizationForm(forms.ModelForm):
class Meta:
model = Organization
fields = ('name',)
def __init__(self, *args, **kwargs):
super(OrganizationForm, self).__init__(*args, **kwargs)
self.fields['name'].required = True
def clean(self):
cleaned_data = super(OrganizationForm, self).clean()
name = cleaned_data.get('name')
# views.py
class OrganizationCreateView(CreateView): # inherits from CreateView
form_class = OrganizationForm
template_name = 'create_org.html'
success_url = 'success'
def form_valid(self, form): # validate the form and save the model instance
org = form.save(commit=False)
org.save()
return redirect(reverse('redirect_url'))
# urls.py
from Project.apps.app_name import views as app_views
app_name = 'app_name'
urlpatterns = [
url(r'^create_org/$', app_views.OrganizationCreateView.as_view(), name='create_org'), # as_view() is used for class based views
# create_org.html
<form method="post">
{% crsf_token %}
{{ form.as_p }}
<a href="{% url 'app_name:create_person' %}>Create person</a> # You can link to other create views, and just style the link as a button.
<input type="submit" value="Submit">
</form>
Hope that helps.

Django global variable based on user groups

In my Django project I have a database that is populated from outside Django, but needs the Django functionality to display data in the database in a user friendly manner. The legacy database structure is as follows:
class SomeModel(models.Model):
#some fields
group_access = models.ForeignKey(AccessGroup,on_delete=models.CASCADE()
class AccessGroup(models.Model):
group_name = models.CharField(max_length=200)
The users have a custom User profile with manytomany relationship with group names assigned to the specific user:
class CustomUser(models.Model):
#some values
projects = models.ManyToManyField(AccessGroup)
Currently I am able to display data from all groups a user has access to, but what I am looking for is a way to create a drop down menu so that users can switch between groups without the need to log out or reenter group on every view.
You could try something like this:
AccessGroup.objects.filter(CustomUser__pk=1)
Or
CustomUser.objects.filter(AccessGroup__group_name='GropName')
https://docs.djangoproject.com/en/2.0/topics/db/examples/many_to_many/
you can extend the django user model, somthing like
from django.contrib.auth.models import User
class CustomUser(models.Model):
projects = models.ManyToManyField(AccessGroup)
class UserProfile(models.Model):
user = models.OneToOneField(User, unique=True)
custom_user = models.ForeignKey(CustomUser, unique=False)
class SomeModel(models.Model):
#some fields
group_access = models.ForeignKey(AccessGroup,on_delete=models.CASCADE()
class AccessGroup(models.Model):
group_name = models.CharField(max_length=200)
then something like this to get the data in your view
def index(request):
AccessGroup.objects.filter(user__id=persion(request.user).id)
I'll assume you know how to get the list of groups, and are just looking as to how to get this list into templates. If not, let me know and I'll explain that as well.
If you're trying to get a global variable into templates, there are really 3 main options:
Make a custom template tag that takes the current user as input, and generates this list as output.
Use Middleware to generate the list, and append it to the current context for each request
Use a method on your user class, or a mixin of it (really easy if you use a custom user class), and just call that method as user.method in your templates. Remember to exclude parentheses from the method call (only in templates), and keep in mind that this method shouldn't accept any parameters other than self.
Thank you everybody for getting me on the right track. What I ended up doing is writing a context processor for checking the user permissions:
#context_processors.py
def check_groups(request):
group_check = AccessGroup.objects.values('id','group_name').filter(projects=request.user.id)
return {
'group_check': group_check,
}
Afterwards I created a Bootstrap-select dropdown in my base.html
<select class="selecpicker">
<optgroup>
<option data-hidden="true">Choose group</option>
{% for grpup in group_check %}
<option val="group.id">{{ group.group_name }}</option>
{% endfor %}
</optgroup>
And the it is just a matter of users using it as means to switch access groups in views and passing the value via ajax to any other template views I come across.
Not the 100% what I was looking for, but it works and my users are happy.

Better to use 3rd Party Row-Level Permission App or to Filter Query Results in a Django View?

I am writing a Django app that involves the creation of documents. I have a few requirements:
Check to see if the user is able to view any documents at all.
If the user is allowed to view documents, only allow them to view documents they have permission for.
I have come up with two solutions and am wondering if one is philosophically/practically better than the other.
The two solutions I've come up with are:
Solution One (using third-party Django-Guardian)
Models.py
class Document(models.Model)
owner = models.ForeignKey(User)
document_name = models.CharField(max_length=60)
document_content = models.TextField()
class Meta:
permissions = (
('view_document', 'View Document'),
)
views.py
#permission_required('document.view_document', (Document, 'pk', 'document_id'))
def view_document(request, document_id):
document = Document.objects.get(pk=document_id)
return render_to_response("native/view_events.html",
{
'document' : document,
}, context_instance=RequestContext(request))
The downside I see to solution number one is that I have to explicitly set permissions every time I create an object, plus I have to hit the database twice: once to check permissions and again to retrieve the document object.
Solution Two (using built-in Django permissions)
Models.py
class Document(models.Model)
owner = models.ForeignKey(User)
document_name = models.CharField(max_length=60)
document_content = models.TextField()
viewers = models.ManyToManyField(User)
class Meta:
permissions = (
('view_document', 'View Document'),
)
views.py
#permission_required('document.view_document')
def view_document(request, document_id):
document = Document.objects.filter(pk=document_id, viewers__pk=request.user.pk)[0]
return render_to_response("native/view_events.html",
{
'document' : document,
}, context_instance=RequestContext(request))
The downside I see to solution number one is that I have to do two checks, one to see if they are able to view documents at all, and one to see if they can view the particular document. A plus side to this is that if I have an admin that I want to be able to view all documents, I don't need to explicitly grant permission to each one; I can just give him the 'view document' permission.
It seems both solutions have their pros and cons. Is there one that is better in theory/practice?
I find the second approach better. Since, you can check the model level permission on the object. Though in the first I guess, you should be able to accomplish similar things. I am not sure but if django-guardian provides a way to check the permission inside the view code instead of the decorator. You could manually check for Model Level Permission.
For example,
def view_doc(request, doc_id):
if user can not view doc: #Model Level Permission
return HttpResponse("Sorry can not view")
if check django-guardian permission #Object Level Permission
return HttpResponse("Can not view doc")
#further code
But I would suggest the second approach since, you can create an api just to check permission and customize it which is more scalable.

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

Simple JOIN in Django

I'm trying to accomplish something akin to twitter on my website, where one user can follow another one. I want to select all User records that are following a User with a given ID. My follow relationship model look like:
class Following(models.Model):
user = models.ForeignKey(User, related_name='is_following')
followed = models.ForeignKey(User, related_name='is_followed')
And I'm using the django.contrib.auth.models User class.
Assuming I have a User object named "myUser" representing the followed person, what's the appropriate query to get everyone following them?
mipadi's answer is missing '.all'.
In a view, the queryset
followers = my_user.is_followed.all()
returns a list of Following objects where my_user is being followed. To get the user that is following from a Following object f, use f.user
If followers is in the template context, you could do the following.
{% for f in followers %}
{{ f.user }}
{% endfor %}
You might find the Django documentation for many to many relationships useful.
That's where the related_name attribute comes into play -- it allows you to follow a relationship backwards. So, to get all the users following a particular user, you'd use my_user.is_followed.
Obviously you might want to rename your attributes to reflect these relationships, since followed and is_followed might be a bit confusing, but that's how you'd do it.