i am a beginner to Django i am trying to use permissions to permit access to specific view functions via a decorator, for specific user types only. Right now i am totaly confused by all kinds of stuff i have read about and seam not to figure out how i should do this.
I have two different kinds of users let it be UserTypeONE and UserTypeTWO.
UserTypeONE and UserTypeTWO should have access to specific views only.
Here is my code:
myuserTypes.py
class UserTypeONE(models.Model):
lieOtO_User = models.OneToOneField(settings.AUTH_USER_MODEL)
lie_SomeAttribute= models.CharField(max_length=300, help_text ='Name')
class Meta:
permissions = (('Can_View_MyShop', 'Can see Shop View'),)
class UserTypeTWO(models.Model):
lieOtO_User = models.OneToOneField(settings.AUTH_USER_MODEL)
lie_SomeOtherAttribute= models.CharField(max_length=300, help_text ='Name')
class Meta:
permissions = (('Can_View_Targets', 'Can see the Targets'),)
Here is what i am trying to do in my views.py
#login_required
#permission_required('UserTypeONE.Can_View_MyShop', raise_exception=True)
def MyShopView(request):
#do something
i also tried
#user_passes_test(lambda u: u.usertypeone.permission('Can_View_MyShop'))
As you guys can see i am an absolute beginner unfortunately all documentations and examples havent done me any good instead i am even more confused.
I would really appreciate help on this.
I would use user_passes_test() here since you specifically want to restrict specific views.
First, define a couple of functions that return True when you're dealing with a user who should be able to see your content. It looks like your UserTypeOne and UserTypeTwo models extend the base User model with a one-to-one relationship, so you can use hasattr to check if a given base user has one of those attributes:
def type_one_only(user):
if hasattr (user, 'usertypeone'):
return True
else:
return False
def type_two_only(user):
#same thing without if/else
return hasattr(user, 'usertypetwo')
Now when you have a view that you want to restrict to one user type, you can add a user_passes_test decorator before it:
#user_passes_test(type_one_only, login_url='/')
def my_view(request):
...
login_url is where a user will be sent if they do not pass the test you've indicated.
Related
The has_object_permission method of a Permission on DRF obviously does not get executed on Create, since the object does not exist yet. However, there are use cases where the permission depends on a related object. For example:
class Daddy(models.Model):
name = models.CharField(max_length=20)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
class Kiddy:
title = models.CharField(max_length=12)
daddy = models.ForeignKey(Daddy, on_delete=models.CASCADE)
If we only want to allow the owner of Daddy to create Kiddy of that Daddy, we would have to validate that somewhere.
I know this a really common discussion, also mentioned on this question and in many many more. It is also discussed on DRF GitHub itself where a docs update is done for that purpose, and referring to DRF docs it solves the problem here with the following sentence:
... In order to restrict object creation you need to implement the permission check either in your Serializer class or override the perform_create() method of your ViewSet class.
So, referring to DRF docs, we could do one of the following solutions:
class KiddySerializer(viewsets.ModelViewSet):
validate_daddy(self, daddy):
if not daddy.owner == self.context['request'].user:
raise ValidationError("You cannot create Kiddies of that Daddy")
return daddy
or
class KiddyViewSet(ModelViewSet):
def perform_create(self, serializer):
if (self.request.user != serializer.validated_data['daddy'].owner)
raise ValidationError("You cannot create Kiddies of that Daddy")
serializer.save()
Now, there is a problem which also brings up my question. What if I care about the information that is being shared to the user on an unauthorized request. So, in the cases where the Daddy does not exist, the user will get:
Invalid pk \"11\" - object does not exist and in the cases that the object exists but the user does not have access, it will return You cannot create Kiddies of that Daddy
I want to show the same message in both cases:
The Daddy does not exist or you don't have permission to use it.
It can be possible if I use a PermissionClass like below:
class OwnsDaddy(BasePermission):
def has_permission(self, request, view):
if not Daddy.objects.allowed_daddies(request.user).filter(pk=request.data['daddy']).exists():
return False
this will also work, but since the permissions are validated before serializer, if the ID of daddy passed by the user is incorrect (let's say string), a 500 error will be caused. We can prevent that by a try-except clause, but still it doesn't feel right.
So, at the end. What would be a good approach to this problem?
Validate in your serializer instead of view. You can validate a particular field in validate_<field> or as a whole in validate function
class YourModleSerializer(serializers.ModelSerializer):
def validate_field(self, value):
return value
def validate(self, attrs):
validated_data = super().validate(attrs)
return validated_data
Creating custom field relation was the right approach for my case.
from apps.models import Daddy
from rest_framework.serializers import PrimaryKeyRelatedField
class DaddyRelatedField(PrimaryKeyRelatedField):
default_error_messages = {
"required": _("This field is required."),
"does_not_exist": _("The Daddy does not exist or you don't have permission to use it."),
"incorrect_type": _("Incorrect type. Expected pk value, received {data_type}."),
}
def get_queryset(self):
if self.read_only:
return None
queryset = Daddy.objects.all()
return queryset.allowed_daddies(self.context["request"].user)
and then on serializer
class KiddySerializer(ModelSerializer):
daddy = DaddyRelatedField()
I have a code that allows user to like the post but I realized the user can like the post more than once which I don't want. How do I restrict this?
my code
#login_required
def like_post(request, pk):
if pk:
liked_post = Post.objects.get(id=pk)
count = liked_post.likes
count += 1
liked_post.likes = count
liked_post.save()
return redirect('/community/post/%s' %liked_post.id)
what I tried
adding something like this....but not sure
if post.likes.filter(id=user.id).exists():
post.likes.remove(user)
else:
post.likes.add(user)
You can use the unique_together meta class option. This would raise a validation error which you could pass over if you didn't want to report an error.
In your Likes model (if you have one) include something like the following:
class Meta():
unique_together = ('id', 'user')
Or add a 'liked_by' attribute to Post as a many to many field to users which should manage the uniqueness stuff for you. The relationship can only exist once. You could use Post.liked_by.count() to get the number of likes, but I'd have no issue seeing the like count as its own attribute.
As the title suggests I'd like to know if and how I can override the get and post methods of Tastypie.
For example, every time a user sends over a json file at the API endpoint, I don't want anything to be stored in the models and instead only return a small message back.
How can I do this?
Thanks.
This example coming directly from Tastypie Cookbook:
from tastypie.utils import now
class MyResource(ModelResource):
class Meta:
queryset = MyObject.objects.all()
def get_object_list(self, request):
return super(MyResource, self).get_object_list(request).filter(start_date__gte=now)
Similar approach can be utilized for POST etc. as well. Hope it helps :)
I'm currently writing a site which uses django-guardian to assign object-level permissions which are used throughout the site.
Here is the desired functionality:
I'd like for a user to have permissions to edit a single object (or multiple objects). For example, if there is a user named "Joe", and a model named "Partyline", I may give "Joe" specific permissions to "change_partyline" for 3 specific "Partyline" objects.
When Joe logs into the Django admin panel, I'd like him to be able to edit ONLY his 3 specific "Partyline" objects, since those are the only things he has permission to edit.
Here is the current functionality:
I can assign Joe change_partyline permissions to 3 Partyline objects--no problem. And Joe can log into the admin panel just fine. The problem is that since Joe doesn't have "global" permissions to change ALL partylines, the admin panel says that he does not have any permissions, and won't let him edit anything. I'd like to find a way for the admin to recognize that Joe has permissions to edit only 3 specific objects, and let him view and edit only those objects which he has permissions to work on.
I'd LOVE to find a way to make this work. I'm using the admin extensively for my users to manage things right now, and it would really break presentation to have to move certain functionality out of the admin to other areas on the site.
If you have any suggestions, please let me know!
For reference, here is some shell output demonstrating that the user has change_partyline permissions on a Partyline object:
>>> from django.contrib.auth.models import User
>>> u = User.objects.get(id=2)
>>> from apps.partylines.models import Partyline
>>> p = Partyline.objects.get(id=3)
>>> u.has_perm('partylines.change_partyline', p)
True
And here's my partylines.admin module (which shows how the Partyline module is populated in the admin):
from django.contrib import admin
from guardian.admin import GuardedModelAdmin
from apps.partylines.models import Partyline
class PartylineAdmin(GuardedModelAdmin):
list_display = ('did', 'name', 'listed')
ordering = ('did',)
search_fields = ('did', 'name')
admin.site.register(Partyline, PartylineAdmin)
I've asked a similar question to Lukazs (guardian's author) and he told me that this feature is coming on a future release (see user_can_access_owned_objects_only property on this commit and the related issue). If you're not willing to wait, maybe you can just install the source on the master branch.
Have you thought about overriding queryset on your model? In my case was just enough:
# models.py
from django.db import models
from django.contrib.auth import models as auth_models
class MagazineUser(models.Model):
user = models.ForeignKey(auth_models.User, unique=True)
class Magazine(models.Model):
managers = models.ForeignKey(MagazineUser)
# admin.py
class MagazineAdmin(admin.ModelAdmin):
def queryset(self, request):
qs = super(admin.ModelAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
user_qs = MagazineUser.objects.filter(user=request.user)
return qs.filter(managers__in=user_qs)
That functionality is known as "row-level permissions", and there is a short explanation of some of the admin methods you'll need to override on the Django wiki here.
For your specific use case, I'd guess you want a ManyToMany relationship between Partyline and User, so that the objects allowed for a user can be retrieved via:
objects = request.user.partyline_set.all()
It might simply be enough to override queryset() and return that object list.
I want to make some of my Django global settings configurable through the admin interface.
To that end, I've decided to set them as database fields, rather than in settings.py.
These are the settings I care about:
class ManagementEmail(models.Model):
librarian_email = models.EmailField()
intro_text = models.CharField(max_length=1000)
signoff_text = models.CharField(max_length=1000)
These are one-off global settings, so I only ever want there to be a single librarian_email, intro_text etc floating around the system.
Is there a way I can prevent admin users from adding new records here, without preventing them from editing the existing record?
I guess I can do this by writing a custom admin template for this model, but I'd like to know if there's a neater way to configure this.
Could I use something other than class, for example?
Thanks!
Please see this question on "keep[ing] settings in database", where the answer seems to be django-dbsettings
Update
Just thought of another option: you can create the following model:
from django.contrib.sites.models import Site
class ManagementEmail(models.Model):
site = models.OneToOneField(Site)
librarian_email = models.EmailField()
intro_text = models.CharField(max_length=1000)
signoff_text = models.CharField(max_length=1000)
Because of the OneToOneField field, you can only have one ManagementEmail record per site. Then, just make sure you're using sites and then you can pull the settings thusly:
from django.contrib.sites.models import Site
managementemail = Site.objects.get_current().managementemail
Note that what everyone else is telling you is true; if your goal is to store settings, adding them one by one as fields to a model is not the best implementation. Adding settings over time is going to be a headache: you have to add the field to your model, update the database structure, and modify the code that is calling that setting.
That's why I'd recommend using the django app I mentioned above, since it does exactly what you want -- provide for user-editable settings -- without making you do any extra, unnecessary work.
I think the easiest way you can do this is using has_add_permissions function of the ModelAdmin:
class ContactUsAdmin(admin.ModelAdmin):
form = ContactUsForm
def has_add_permission(self, request):
return False if self.model.objects.count() > 0 else super().has_add_permission(request)
You can set the above to be any number you like, see the django docs.
If you need more granularity than that, and make the class a singleton at the model level, see django-solo. There are many singleton implementations also that I came across.
For StackedInline, you can use max_num = 1.
Try django-constance.
Here are some useful links:
https://github.com/jezdez/django-constance
http://django-constance.readthedocs.org/en/latest/
I'd take a page out of wordpress and create a Model that support settings.
class Settings(models.Model):
option_name = models.CharField(max_length=1000)
option_value = models.CharField(max_length=25000)
option_meta = models.CharField(max_length=1000)
Then you can just pickle (serialize) objects into the fields and you'll be solid.
Build a little api, and you can be as crafty as wordpress and call. AdminOptions.get_option(opt_name)
Then you can just load the custom settings into the runtime, keeping the settings.py module separate, but equal. A good place to write this would be in an __init__.py file.
Just set up an GlobalSettings app or something with a Key and Value field.
You could easily prevent admin users from changing values by not giving them permission to edit the GlobalSettings app.
class GlobalSettingsManager(models.Manager):
def get_setting(self, key):
try:
setting = GlobalSettings.objects.get(key=key)
except:
raise MyExceptionOrWhatever
return setting
class GlobalSettings(models.Model):
key = models.CharField(unique=True, max_length=255)
value = models.CharField(max_length=255)
objects = GlobalSettingsManager()
>>> APP_SETTING = GlobalSettings.objects.get_setting('APP_SETTING')
There are apps for this but I prefer looking at them and writing my own.
You can prevent users from adding/deleting an object by overriding this method on your admin class:
ModelAdmin.has_add_permission(self, request)
ModelAdmin.has_delete_permission(self, request, obj=None)
Modification of #radtek answer to prevent deleting if only one entry is left
class SendgridEmailQuotaAdmin(admin.ModelAdmin):
list_display = ('quota','used')
def has_add_permission(self, request):
return False if self.model.objects.count() > 0 else True
def has_delete_permission(self, request, obj=None):
return False if self.model.objects.count() <= 1 else True
def get_actions(self, request):
actions = super(SendgridEmailQuotaAdmin, self).get_actions(request)
if(self.model.objects.count() <= 1):
del actions['delete_selected']
return actions
I had basically the same problem as the original poster describes, and it's easily fixed by overriding modelAdmin classes. Something similar to this in an admin.py file easily prevents adding a new object but allows editing the current one:
class TitleAdmin(admin.ModelAdmin):
def has_delete_permission(self, request, obj=TestModel.title):
return False
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=TestModel.title):
return True
This doesn't prevent a user from posting a form that edits data, but keeps things from happening in the Admin site. Depending on whether or not you feel it's necessary for your needs you can enable deletion and addition of a record with a minimum of coding.