I've added some custom permissions to my Post model.
I've also created a form to add/edit groups with only this custom permissions:
class GroupFornm(forms.ModelForm):
permissions = forms.MultipleChoiceField(choices=Post._meta.permissions)
class Meta:
model = Group
fields = '__all__'
It works because I can see and select only my custom permissions but when I try to save the form I got the error:
invalid literal for int() with base 10: 'can_view'
What am I doing wrong? It seems that this form field waits for (int, str) pair but documentation says that as usually, (str, str) should work.
Edit
Post._meta.permissions:
(('can_view', 'Can see tickets'), ('can_edit', 'Can edit tickets'), ('can_delete', 'Can delete tickets'), ('can_create', 'Can add new tickets'))
The problem is not really related to the form itself, but the fact that you somehow need to translate those permissions into Permission objects that should be stored in the Group instance (the one that this ModelForm is managing).
I think displaying the options is not a problem. But if a user later for example performs a POST request, with the options (like can_write), then the question is how the Form should translate these into Permission objects (or the primary keys of Permission objects).
In that case you need to coerce the name of the permissions to Permission objects, or the ids of Permission objects. We can for example use a TypedMultipleChoiceField, and coerce with:
def get_permission_from_name(name):
return Permission.objects.get(name=name)
class GroupFornm(forms.ModelForm):
permissions = forms.TypedMultipleChoiceField(
choices=Post._meta.permissions,
coerce=get_permission_from_name,
)
class Meta:
model = Group
fields = '__all__'
Note that the above is not really a very efficient implementation, since it requires a query for every value send. Furthermore in case no permission with that name exists, then this will raise an error.
If you want to construct Permissions on the fly (in case these are not yet constructed), then you can change the function to:
def get_permission_from_name(name):
return Permission.objects.get_or_create(
name=name,
defaults={
'content_type': ContentType.objects.get_for_model(Post),
'codename': name
}
)
Related
I created a model Checkout on my project, with a CheckoutType to handle the requests, but now i need a Profile, that is basically just getting many of the fields on Checkout. The problem is that Checkout and Profile will be retrieved by users with very different permissions, and the while the first one will have the right ones, the second one must not have them. so i went with creating 2 types:
Checkout:
class CheckoutType(ModelType):
class Meta:
model = Checkout
interfaces = [graphene.relay.Node]
connection_class = CountableConnection
permissions = ['app.view_checkout']
filter_fields = {
'zone': ['exact'],
'vehicle__mark': ['exact'],
'status': ['exact']
}
Profile:
class ProfileFilter(django_filters.FilterSet):
class Meta:
model = Checkout
fields = ['zone','status']
#property
def qs(self):
# The query context can be found in self.request.
return super(ProfileFilter, self).qs.filter(salesman=self.request.user)
class ProfileType(ModelType):
class Meta:
model = Checkout
interfaces = [graphene.relay.Node]
connection_class = CountableConnection
filterset_class = ProfileFilter
The thing here is that, the first one shouldn't filter, and just be a regular schema, while the second one should filter by the user that made the request, that and the permissions is the reason i use 2, but as soon as i implemented, all the tests i did for the Checkout Type started to fail, since it seems it tries to use the ProfileType. I searched a little, and it seems that relay only allows a type per model in Django, so this approach doesn't seems possible, but i'm not sure how to overwrite the CheckoutType on another schema, or how to make a second Type with different permissions and different filters. Does someone knows if this is possible?
Just in case someone is on the same boat, i think i found a way to make it work, but with a different approach, i just modified the CheckoutType a little:
class CheckoutType(ModelType):
# Meta
#classmethod
def get_queryset(cls, queryset, info):
if info.context.user.has_perm('app.view_checkout'):
return queryset
return queryset.filter(salesman=info.context.user)
Basically here i remove the permission from the Meta, since i don't want to check that there, and then i overwrite the get_queryset() to check if the user has the perms, if that's the case, then just return the normal query, but if not just filter(And any additional thing you want to do for people without the permission). I'm not sure if there's a better way, but definitely did the job.
I am creating an application in Django REST Fremework, in which the user can add an order.
I would like the serializer to set a reference to the user based on the token and complete the "Client" model field.
It's actually works with HiddenField, as shown in the documentation.
(Link: https://www.django-rest-framework.org/api-guide/fields/#hiddenfield)
class OrderSerializer(serializers.ModelSerializer):
client = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = Order
fields = '__all__'
The problem is that when I fetch a single order or list of orders, Client field is of course hidden becouse of HiddenField type.
curl -X GET http://127.0.0.1:8000/api/orders/12
{
"id":12,
"name":"sprzatanie ogrodka",
"description":"dupa",
"price":"12.20",
"work_time_hours":2,
"work_time_minutes":50,
"workers_needed_num":3,
"coords_latitude":"-1.300000",
"coords_longitude":"1.100000",
"created_at":"2020-03-08T13:20:16.455289Z",
"finished_at":null,
"category":1,
"workers":[]
}
I would like the field to still capture reference to the logged in user, but at the same time to be visible when returning data from the API.
What serializers field type I need to use?
Thanks!
Going through the documentation i found: https://www.django-rest-framework.org/api-guide/validators/
Using a standard field with read_only=True, but that also includes a default=… argument. This field will be used in the serializer output representation, but cannot be set directly by the user.
this is what you need i think.
So whatever field type you have set in Model can be used with read_only=True
For example:
client = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
Hope this helps
I want to create a viewset/apiview with a path like this: list/<slug:entry>/ that once I provide the entry it will check if that entry exists in the database.
*Note: on list/ I have a path to a ViewSet. I wonder if I could change the id with the specific field that I want to check, so I could see if the entry exists or not, but I want to keep the id as it is, so
I tried:
class CheckCouponAPIView(APIView):
def get(self, request, format=None):
try:
Coupon.objects.get(coupon=self.kwargs.get('coupon'))
except Coupon.DoesNotExist:
return Response(data={'message': False})
else:
return Response(data={'message': True})
But I got an error: get() got an unexpected keyword argument 'coupon'.
Here's the path: path('check/<slug:coupon>/', CheckCouponAPIView.as_view()),
Is there any good practice that I could apply in my situation?
What about trying something like this,
class CheckCouponAPIView(viewsets.ModelViewSet):
# other fields
lookup_field = 'slug'
From the official DRF Doc,
lookup_field - The model field that should be used to for performing
object lookup of individual model instances. Defaults to pk
I got two models:
class ContactGroup(models.Model):
name = models.CharField(max_length=40)
class Meta:
permissions=(('view_group_contacts', 'View contacts from group'))
class Contact(models.Model):
name = models.CharField(max_length=40)
group = models.ForeignKey(ContactGroup)
class Meta:
permissions=(('view_contact', 'View contact'))
How can I make django guardian consider ContactGroup permissions when I'm for example doing `get_objects_for_user(User, 'appname.view_contact) but still retain option for changing permission on individual Contact?(not for excluding, only to give permission to view single contact when user don't have the permission for whole group)
Sorry, such behaviour is not supported by django-guardian. As for has_perm - it would be extremely inefficient to use it for querysets as we would need to perform >=1 query for each row in a table.
You could however perform get_objects_for_user firstly for ContactGroup, then for Contact and extend last queryset with results from the first one. Something like:
contact_groups = get_objects_for_user(user, 'appname.view_group_contacts', ContactGroup)
contacts = get_objects_for_user(user, 'appname.view_contact', Contact)
There is still problem of merging those but well, it's possible.
Very ugly workaround, it does not take for account changes in individual objects (it resets all permission to ContactGroup permissions if remove= False). But It can be rewritten to preserve changes if needed. I plan to attach it to "Sync Permissions with group" button so it will be fired only at user request. Main pro is its working with get_objects_for_user as intended.
def syncPerms(source, remove=False):
if not isinstance(source, ContactGroup):
return False
contacts= source.client_set.all()
user_perms=get_users_with_perms(source, attach_perms=True)
for contact in contacts:
for user, perm in user_perms.iteritems():
if u'view_group_contacts' in perm:
assign_perm('view_contact', user,client)
else:
if remove:
remove_perm('view_contact', user, client)
Last Month i posted question on stackoverflow and on Django-Users group on G+ and on django website too. But i didn't find any answer that can solve my problem. What i want to do is to add new permission named as view in django admin panel, so user can only view data!. I also followed different patches from django website and tried django-databrowse but nothing works as expected. I then finally decide to edit views of auth/admin. Now what i am going to do is to add view permission like:
1. Added 'view' to default permission list
#./contrib/auth/management/init.py
def _get_all_permissions(opts):
"Returns (codename, name) for all permissions in the given opts."
perms = []
for action in ('add', 'change', 'delete', 'view'):
perms.append((_get_permission_codename(action, opts), u'Can %s %s' % (action, opts.verbose_name_raw)))
return perms + list(opts.permissions)
2. Test the 'view' permission is added to all models
run manage.py syncdb
After this i can assign only view permission to user. Now this view permission must work too. So i am writing this code: in view.py of django-admin
for per in request.user.user_permissions_all():
print per
This code prints permissions assigned to login user like auth | permission | can view department etc
Now i can get permission type and model name by splitting this sentence. I will get all the model name of application and will match that which data must b visible. This is again not what i really need but can work.
So my question is :
* Is this is what i should do or is there any other way too. I just want a solution that must works as expected. Need Your Assistance *
Adding 'view' permission to default permissions list
Your solution works, but you should really avoid editing source code if possible. There's a few ways to accomplish this within the framework:
1. Add the permission during post_syncdb():
In a file under your_app/management/
from django.db.models.signals import post_syncdb
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import Permission
def add_view_permissions(sender, **kwargs):
"""
This syncdb hooks takes care of adding a view permission too all our
content types.
"""
# for each of our content types
for content_type in ContentType.objects.all():
# build our permission slug
codename = "view_%s" % content_type.model
# if it doesn't exist..
if not Permission.objects.filter(content_type=content_type, codename=codename):
# add it
Permission.objects.create(content_type=content_type,
codename=codename,
name="Can view %s" % content_type.name)
print "Added view permission for %s" % content_type.name
# check for all our view permissions after a syncdb
post_syncdb.connect(add_view_permissions)
Whenever you issue a 'syncdb' command, all content types can be
checked to see if they have a 'view' permission, and if not, create
one.
SOURCE: The Nyaruka Blog
2. Add the permission to the Meta permissions option:
Under every model you would add something like this to its Meta options:
class Pizza(models.Model):
cheesiness = models.IntegerField()
class Meta:
permissions = (
('view_pizza', 'Can view pizza'),
)
This will accomplish the same as 1 except you have to manually add it to each class.
3. NEW in Django 1.7, Add the permission to the Meta default_permissions option:
In Django 1.7 they added the default_permissions Meta option. Under every model you would add 'view' to the default_permissions option:
class Pizza(models.Model):
cheesiness = models.IntegerField()
class Meta:
default_permissions = ('add', 'change', 'delete', 'view')
Test the 'view' permission is added to all models
As for testing the whether a user has the permission, you can test on the has_perm() function. For example:
user.has_perm('appname.view_pizza') # returns True if user 'Can view pizza'