Despite having read several things on this topic, i'm seeing myself stuck.
I have a Django Rest Framework Application. I have this model :
class MyModel(Model):
name = CharField(_('name'), max_length=50)
type = CharField(_('type'), max_length=50)
users_selected = ManyToManyField(User)
Which i'm trying to serialize. I don't want to expose the users_selected as is seeing as it has all the users of the application whom selected this specific model, but just be able to put a boolean saying if it's selected for the current user, which i achieved by doing :
class MySerializer(ModelSerializer):
is_selected = SerializerMethodField('user_select')
def user_select(self, obj):
request = self.context['request']
return obj.users_selected.filter(id=request.user.id).exists()
class Meta(object):
model = MyModel
exclude = ('users_selected',)
This is working fine, but now i want to do it the other way around, meaning i want my client to be able to send a request with a is_selected set to true or false and then modify my users_selected accordingly. The SerializerMethodField implies the fact that this is a read-only field.
How should I achieve this? I've tried to search by using the restore_object method, but was unable to achieve anything...
Thanks.
To me, it feels a little strange to have MyModel point to all of the Users who selected it. This would be simpler if you subclassed the User, and gave the custom User either a Foreign key or a ManyToManyField to the MyModels selected.
If you did it this way, a single User could access his own selections, and there would be no permissions problems. The only time someone would need to access Users directly from the MyModel object would be for administrative work, at which time it would be perfectly fine to expose all of the users in the list. You could even have a separate view, something like MyModelUsersSelected, which could look like:
class MyModelUsersSelected(generics.ListAPIView):
serializer_class = UserSerializer
permission_classes = (permissions.IsAdminUser,)
def get_queryset(self):
return MyModel.objects.get(id=self.kwargs['pk']).user_set.all()
This would give you all of the Users who pointed to the MyModel instance passed as a pk in the url. Since this view is separate from the MyModel and User views, permissions can be set to admin users only.
Related
I am building a Django web app where I want two things:
Use Django's in-built User model for Django's Admin app usage (store owner)
Use DRF's Token Auth on a custom User model that I will be naming "Customer" (store customer)
How do I keep both the Authentication systems for the above stated purposes. From what I have read every one asks to override the User model but I don't want to do that. Instead I want to keep both. What strategy should I take up?
PS: It might be me, but I am not able to find any solution for this in DRF's Documentation. If there is please do point me in the right direction.
Django does provide an option for using a custom user model. But you can have one and only one user model.
The process is fairly simple, create your own model inheriting django.contrib.auth.models.AbstractUser and specify the AUTH_USER_MODEL settings variable. The Django admin works pretty well with the "custom user model" concept. The DRF token model also uses the settings.AUTH_USER_MODEL var for its OneToOne relation. So, this is can be a viable solution.
To separate out the user types, you can either use a char field with choices representing the user type or use the existing Django groups mechanism. But, in both cases, you could still only have one user model.
For any specific details, you can have OneToOne relations with different models storing extra info.
Something like this would do,
from django.contrib.auth.models import AbstractUser
from model_utils.choices import Choices # Useful package
from django.utils.functional import cached_property
class User(AbstractUser):
USER_TYPES = Choices(
("store_owner", "Store Owner"),
("customer", "Customer"),
)
...hack...
user_type = models.CharField(choices=USER_TYPES)
...hack...
#cached_property
def is_store_owner(self):
return (
self.user_type == self.USER_TYPES.store_owner
and self.store_owner is not None
)
#cached_property
def is_customer(self):
return (
self.user_type == self.USER_TYPES.customer
and self.customer is not None
)
class StoreOwner(models.Model):
user = models.OneToOneField(
"yourapp.User",
related_name="store_owner",
)
# ...extra store owner details...
class Customer(models.Model):
user = models.OneToOneField(
"userapp.User",
related_name="customer",
)
# ...extra customer details...
I'm fairly new to Django and Django Rest Framework and I can't figure out why my code isn't working.
I have a Biz model that has a few fields:
class Biz(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, editable=False)
title = models.CharField(max_length=200)
description = models.TextField()
address = models.CharField(max_length=255, blank=True)
city = models.CharField(max_length=100)
phone = PhoneNumberField()
which I serializer using ModelSerializer:
class BizSerializer(serializers.ModelSerializer):
class Meta:
model = Biz
fields = "__all__"
And I use ModelViewSet to have an endpoint for it:
class BizViewSet(viewsets.ModelViewSet):
queryset = Biz.objects.all()
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = [HasGroupPermission]
required_groups = {
"GET": ["__all__"],
"POST": ["member", "biz_post"],
"PUT": ["member", "biz_edit"],
"PATCH": ["member", "biz_edit"],
}
serializer_class = BizSerializer
You probably noticed HasGroupPermission. It is a custom permission I made to confirm the requesting user is in required group(s) the code is:
def is_in_group(user, group_name):
"""
Takes a user and a group name, and returns `True` if the user is in that group.
"""
try:
return Group.objects.get(name=group_name).user_set.filter(id=user.id).exists()
except Group.DoesNotExist:
return None
class HasGroupPermission(permissions.BasePermission):
"""
Ensure user is in required groups.
"""
def has_permission(self, request, view):
# Get a mapping of methods -> required group.
required_groups_mapping = getattr(view, "required_groups", {})
# Determine the required groups for this particular request method.
required_groups = required_groups_mapping.get(request.method, [])
# Return True if the user has all the required groups or is staff.
return all(
[
is_in_group(request.user, group_name)
if group_name != "__all__"
else True
for group_name in required_groups
]
) or (request.user and request.user.is_staff)
However, when I make a GET request, the permission function works like it's supposed to and allows everyone to make the request, and when i make a POST request, the permission function also works perfectly (if user isn't in both "member" and "biz_post" groups the request is denied).
The problem arises when I try other methods such as PUT, PATCH, and DELETE. Why is this issue happening? Half the methods work and the other half (sorta) don't. My knowledge in DRF is limited at the moment, and I can't seem to solve the issue.
I realized my problem which I found very silly. My BizViewSet is actually a ViewSet and I didn't realize that I have to make PATCH, PUT, and DELETE requests to the object link (as in localhost:8000/api/biz/$id). Since my User serializer isn't a ViewSet I thought the patch method works the same way which was I pass a primary key in JSON along with the data I wanted to patch but ViewSets are different and I didn't know that. Silly.
Hi you can use DjangoModelPermissions instead of HasGroupPermission
(at the first you must import it)
from rest_framework.permissions import DjangoModelPermissions
This permission check that user have permission for PUT, POST and DELETE
All user have GET permission
You must set permission for user in admin or set permission for group of user
I hope it helps you
has_permission method not provide object-level permission and PUT and PATCH need object-level permission.
You must create object-level permissions, that are only run against operations that affect a particular object instance by using has_object_permission method of permissions.BasePermission class.
See this link.
Hope it helped.
If user 1 creat this ticket :
mywebsite/manager/tickets/ticket-from-user-1/
And user 2 create that :
mywebsite/manager/tickets/ticket-from-user-2/
How can I prevent user 1 to access the ticket from user 2 or other users by typing it in the url?
views.py
class TicketDisplay(LoginRequiredMixin, DetailView):
model = Ticket
template_name = 'ticket_detail.html'
context_object_name = 'ticket'
slug_field = 'slug'
def get_context_data(self, **kwargs):
context = super(TicketDisplay, self).get_context_data(**kwargs)
context['form_add_comment'] = CommentForm()
return context
url.py
url(r'^manager/tickets/(?P<slug>[-\w]+)/$',views.TicketDetail.as_view(), name='ticket_detail')
I recently implemented this functionality in a project. It can be done by using automatically generated uuid's. Django has a built-in model field for this, or you can use a slug field and give it a default value. Here is a quick example.
In your models.py file, import the uuid library and then set the default value of your slug field to be uuid.uuid4.
models.py:
import uuid
class Ticket(models.Model):
uuid = models.SlugField(default=uuid.uuid4, editable=False)
...
In urls.py, just use the uuid field as if it were a pk. Something like this:
url(r'^manager/tickets/(?P<uuid>[0-9a-z-]+)/?$', TicketDetail.as_view(), name='ticket-detail'),
In your detail, update, and delete views, you will need to make sure and set these two attributes so that Django knows which field to use as the slug:
slug_field = 'uuid'
slug_url_kwarg = 'uuid'
Then in your templates and whenever you need to retrieve an object for the kwargs, just use the uuid instead of the pk.
Note that in addition to this, you should also do all you can with permissions to block users from seeing other pages. You may be able to block certain accounts from viewing other peoples details. For instance, you could probably write a permissions mixin to check whether request.user matches up with the object that the view is handling.
tldr This is assuming that you have some kind of relation to a user on your Ticket model:
class SameUserOnlyMixin(object):
def has_permissions(self):
# Assumes that your Ticket model has a foreign key called user.
return self.get_object().user == self.request.user
def dispatch(self, request, *args, **kwargs):
if not self.has_permissions():
raise Http404('You do not have permission.')
return super(SameUserOnlyMixin, self).dispatch(
request, *args, **kwargs)
Finally, stick it on to your view like this:
class TicketDisplay(LoginRequiredMixin, SameUserOnlyMixin, DetailView):
...
You will need to make user 1 have something that user 2 can not imitate.
Preferred way would be to use your existing auth methods and check if user accessing the page is allowed to do so.
If you don't have registration on your site then you could generate some random string - secret - and store it with the question. If 'user' has that secret then he's allowed.
This secret string can be stored in cookie or made a part of URL.
Storing it in a cookie has a drawback: if cookie is lost then nobody can access the page. Also user cannot access it from other browser.
Making it a part of url has another drawback: if someone else sees the link, he can access that page too. This could be bad if user's software automatically reports likns he visits somewhere.
Combining these approaches has both drawbacks.
In my Django app, I have the following Models:
class MyModelA(models.Model):
myAttributeA = models.CharField(max_length=255)
class MyModelB(models.Model):
myParent = models.OneToOneField(myModelA)
myAttributeB = models.CharField(max_length=255)
My settings.py has the following Rest Permission settings:
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',),
I also have the following Serializers:
class MyModelASerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = MyModelA
fields = ('myAttributeA',)
class MyModelBSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = MyModelB
fields = ('myParent', 'myAttributeB',)
Now I want to write a simple Django-Rest-Framework API that will allow any user (weather authenticated or not) to retrieve the value of myParent on any instance of MyModelB assuming they have the Primary Key of the MyModelB instance. This should be rather simple. I'm not trying to update, create, or delete anything. I just want to retrieve the value of one attribute of the instance. But I also want my urls.py to match this endpoint to the API:
url(r'^api/AttrMyModelA/(?P<myModelAID>\d+)/?$', SOMETHING HERE. NOT SURE WHAT)
However, I cannot figure out which pattern to use from the tutorial to make this work. Should I use function based or class based views? Should I use Generic API views? Do I need a decorator or no? Mixins? Can someone please show me what my view should look like and what the urls.py endpoint should look like?
Thanks
You require a RetrieveAPIView-derived class to tie things together:
class MyModelAView(RetrieveAPIView):
model = MyModelA
serializer_class = MyModelASerializer
The route mentioned by you would then look like this:
url(r'^api/AttrMyModelA/(?P<pk>\d+)/?$', MyModelAView.as_view())
Note that pk is the default look-up field used by APIView-derived classes when performing single object queries.
You have defined a default permission class (in settings.py), so unless you want to override that you don't need to specify a permission_classes value in your view class.
I have the following models:
class Application(models.Model):
users = models.ManyToManyField(User, through='Permission')
folder = models.ForeignKey(Folder)
class Folder(models.Model):
company = models.ManyToManyField(Compnay)
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='profile')
company = models.ManyToManyField(Company)
What I would like to do is to check whether one of the users of the Application has the same company as the Application (via Folder). If this is the case the Application instance should not be saved.
The problem is that the ManyToManyFields aren't updated until after the 'post-save' signal.
The only option seems to be the new m2m_changed signal. But I'm not sure how I then roll back the save that has already happened.
Another option would be to rewrite the save function (in models.py, because I'm talking about the admin here), but I'm not sure how I could access the manytomanyfield content.
Finally I've read something about rewriting the save function in the admin of the model in admin.py, however I still wouldn't know how you would access the manytomanyfield content.
I have been searching for this everywhere but nothing I come across seems to work for me.
If anything is unclear, please tell me.
Thanks for your help!
Heleen
Because I didn't get a reply from Botondus I decided to ask a new question in the Django Users Google Group and finally got the answer from jaymz.
I figured that Botondus method was the right way of doing it, it just wasn't quite working. The reason that it doesn't work in this case is because I'm using a Through model for the field I would like to do the validation on. Because of some earlier feedback I got on a previously posted question I gathered that first the Application instance is saved and then the ManyToMany instances are saved (I believe this is right, but correct me if I'm wrong). So I thought that, if I would perform the validation on the ManyToMany Field in the Through model, this would not prevent the Application instance being saved. But in fact it does prevent that from happening.
So if you have a ManyToMany Field inline in your model's admin and you would like to do validation on that field, you specify the clean function in the through model, like this:
admin.py
class PermissionInline(admin.TabularInline):
form = PermissionForm
model = Permission
extra = 3
forms.py
class PermissionForm(forms.ModelForm):
class Meta:
model = Permission
def clean(self):
cleaned_data = self.cleaned_data
user = cleaned_data['user']
role = cleaned_data['role']
if role.id != 1:
folder = cleaned_data['application'].folder
if len(filter(lambda x:x in user.profile.company.all(),folder.company.all())) > 0: # this is an intersection
raise forms.ValidationError("One of the users of this Application works for one of the Repository's organisations!")
return cleaned_data
If the validation results in an error NOTHING (neither the application instance, nor the manytomany users instances) is saved and you get the chance to correct the error.
forms.py
class ApplicationForm(ModelForm):
class Meta:
model = Application
def clean(self):
cleaned_data = self.cleaned_data
users = cleaned_data['users']
folder = cleaned_data['folder']
if users.filter(profile__company__in=folder.company.all()).count() > 0:
raise forms.ValidationError('One of the users of this Application works in one of the Folder companies!')
return cleaned_data
admin.py
class ApplicationAdmin(ModelAdmin):
form = ApplicationForm
Edit: Replaced initial (wrong) model validation example with form validation.