I have users signed up to my site. I want to be able to edit their "bids". Basicaly, I want to be able to go to admin/user/user-bid/73 where 73 is a particular user's id and be able to edit their bid info. How can I do that?
I have the following in admin.py:
class UserBidAdmin(admin.ModelAdmin):
def queryset(self, request):
return self.model.objects.filter(user = request.user)
create_modeladmin(UserBidAdmin, name='user-bid', model=Bid)
def create_modeladmin(modeladmin, model, name = None):
class Meta:
proxy = True
app_label = model._meta.app_label
attrs = {'__module__': '', 'Meta': Meta}
newmodel = type(name, (model,), attrs)
admin.site.register(newmodel, modeladmin)
return modeladmin
Thanks.
class BidInline(admin.TabularInline):
model = Bid
class UserWithProfileAdmin(UserAdmin):
..........
inlines = [BidInline]
admin.site.unregister(User)
admin.site.register(User, UserWithProfileAdmin)
Related
I have an exam like this:
class Exam(BaseModel):
...
STATE_CHOICES = (
(PASS, PASS),
(FAILED, FAILED),
(GREAT, GREAT),
state = models.CharField(max_length=15, choices=STATE_CHOICES, default=PASS)
...
Inside Django admin, I want the user with group X to be able to only change the state only from FAILED to PASS.
and users with group Y be able to change the state from FAILED to PASS and PASS to GREAT.
here is my admin.py:
#admin.register(Exam)
class ExamAdmin(NestedModelAdmin):
list_display = ('state',)
Does anyone know a solution for it?
This might work;
class AdminExamForm(forms.ModelForm):
...
options
...
class ExamForm(forms.ModelForm):
...
STATE_CHOICES = (
(PASS, PASS),
(FAILED, FAILED),
(GREAT, GREAT),
state = forms.CharField(choices=STATE_CHOICES)
class Meta:
model = Exam
fields = ('state',)
...
#admin.register(Exam)
class ExamModelAdmin(admin.ModelAdmin):
...
fields = ('state',)
list_display = ('state',)
form = ExamForm
...
def get_form(self, request, obj=None, **kwargs):
if request.user.is_admin or request.user.is_superuser:
return AdminExamForm
else:
return ExamForm
Sorry for giving you a bad example before, didn't have too much time.
This is how you could access the user, if your exam model has one.
from django.contrib.auth import get_user_model
class Exam(BaseModel):
...
STATE_CHOICES = (
(PASS, PASS),
(FAILED, FAILED),
(GREAT, GREAT),
state = models.CharField(max_length=15, choices=STATE_CHOICES, default=PASS)
user = models.ForeignKey(get_user_model(), on_delete=models.RESTRICT)
...
def save(self, *args, **kwargs):
if self.user.is_admin or self.user.is_superuser:
... your logic here
super(Exam,self).save(*args,**kwargs)
To access the request in the create/save method:
Pass it into the kwargs of the create/save method, of the form you want.
Then get the request in the create/save method, and do your logic
request = kwargs.get('request',None)
Edit, to get the request into the model's .save()
Django admin's save model function literally just calls obj.save()
So if you pass request=request into save like so:
def save_model(self, request, obj, form, change):
"""
Given a model instance save it to the database.
"""
obj.save(request=request)
it should work.
Override Admin save:
Override save method of Django Admin
I want to build a REST API where user can do operations on objects, based on their permissions. Consider a record that represents a car - it contains the license number, the type of the car and extra information. Also consider the following user system:
Owners - Who own the car object. Can modify it and delete it.
Editors - Who can only modify the object properties.
Viewers - Can only view the object properties.
Each record can contain multi owners/editors/viewers (The user who created the object should be automatically the owner). Also, owners can add or remove editors/viewers. In my head, I see it as a list of owners/editors/viewers.
So in case of a GET request, I want to be able to return all objects that the user has permissions for, separated into those three categories.
So under my api app, I have the following code:
The models.py file contains:
class CarRecord(models.Model):
type = models.CharField(max_length=50)
license = models.CharField(max_length=50)
The serializers.py file contains:
class CarRecordSerializer(serializers.ModelSerializer):
type = models.CharField(max_length=50)
license = models.CharField(max_length=50)
class Meta:
model = CarRecord
fields = ('__all__')
In view.py I have:
class CarRecordViews(APIView):
def get(self, request):
if not request.user.is_authenticated:
user = authenticate(username=request.data.username, password=request.data.password)
if user is not None:
return Response(data={"error": "invalid username/password"}, status=status.HTTP_401_UNAUTHORIZED)
# return all records of cars that user some type of permission for
Now, I want to get all the records of user that he has permissions to query (along with their permission type). I thought of adding a three extra fields under CarRecord - each one is a list of users that contains that permission type. But I'm not sure if it's the "Django way". So wanted to consult first with SO.
EDIT: I tried to add the following field to my CarRecord class:
owners = models.ManyToManyField(User, related_name='car_owners', verbose_name=('owners'), default=[])
Also I added:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username']
lass CarRecordSerializer(serializers.ModelSerializer):
type = models.CharField(max_length=50)
license = models.CharField(max_length=50)
owners = UserSerializer(many=True)
class Meta:
model = CarRecord
fields = ('__all__')
And the way I create the CarRecordSerializer instance is:
serializer = CarRecordSerializer(data=request.data)
But I get:
{
"error": {
"owners": [
"This field is required."
]
}
}
How to make it work? I guess is my problem is how to serialize a ManyToMany object?
EDIT2: My second attempt is:
class CarRecord(models.Model):
date_created = models.DateTimeField()
type = models.CharField(max_length=50)
license = models.CharField(max_length=50)
owners = models.ManyToManyField(User, related_name='car_owners', verbose_name=('owners'), default=[]))
creator = models.ForeignKey(User,on_delete=models.DO_NOTHING)
# ...
class CarRecordSerializer(serializers.ModelSerializer):
date_created = serializers.DateTimeField(default=datetime.now(timezone.utc))
type = models.CharField(max_length=50)
license = models.CharField(max_length=50)
creator = serializers.StringRelatedField()
owners = serializers.PrimaryKeyRelatedField(many=True,read_only=True)
class Meta:
model = CarRecord
fields = '__all__'
def create(self, validated_data):
self.owners = [self.context['creator']]
record = CarRecord(**validated_data, creator=self.context['creator'])
record.save()
return record
# ...
# In post method:
serializer = CarRecordSerializer(data=request.data, context={ 'creator': user })
But now, in GET method, I filter the owners list with the user and it can't find the objects:
> CarRecord.objects.filter(owners=user)
<QuerySet []>
Also, in the Admin section I see that all of the objects automatically have all the users in the owners/editors/viewers lists. Why is that? Owners should contain only the user that created the record and editors and viewers should be empty lists. In another query, owner can add additional owners/editors/viewers.
Here is the solution I might think is the right one
class CarRecord(models.Model):
date_created = models.DateTimeField(auto_now_add=True)
type = models.CharField(max_length=50)
license = models.CharField(max_length=50)
owners = models.ManyToManyField(User, related_name='car_owners')
creator = models.ForeignKey(User,on_delete=models.DO_NOTHING)
class CarRecordSerializer(serializers.ModelSerializer):
creator = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), required=False)
owners_details = UserSerializer(source='owners', many=True, read_only=True)
class Meta:
model = CarRecord
fields = '__all__'
def create(self, validated_data):
try:
new_owners = validated_data.pop('owners')
except:
new_owners = None
car_record = super().create(validated_data)
if new_owners:
for new_owner in new_owners:
car_record.owners.add(new_owner)
return car_record
In views.py
from rest_frameword import generics
from rest_framework import permissions
class CustomCarRecordPermissions(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method == 'GET':
return True
elif request.method == 'PUT' or request.method == 'PATCH':
return request.user == obj.creator or request.user in obj.owners.all()
elif request.method == 'DELETE':
return request.user == obj.creator
return False
class CarRecordListCreate(generics.ListCreateAPIView):
permission_classes = (IsAuthenticated, )
serializer_class = CarRecordSerializer
queryset = CarRecord.objects.all()
def post(self, request, *args, **kwargs):
request.data['creator'] = request.user.id
return super().create(request, *args, **kwargs)
class CarRecordDetailView(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (CustomCarRecordPermissions, )
serializer_class = CarRecordSerializer
lookup_field = 'pk'
queryset = CarRecord.objects.all()
models is self explanatory;
In CarRecord serializers we set creator as required False and primary key related field so that we can supply request user id before create as shown in views.py post method.
In Detail view we set our custom permission; If the request is GET we allow permissions. But if the request is PUT or PATCH the owners and the creator are allowed. But if it is a delete request only creator is allowed.
I think the django-rest-framework-guardian package fits here. This package is based on django-guardian.
django-guardian is an implementation of object permissions for Django providing an extra authentication backend.
There is no change on your models.py
You should change serializers.py and views.py.
For example, your serializer should look like this
from rest_framework_guardian.serializers import ObjectPermissionsAssignmentMixin
class CarRecordSerializer(ObjectPermissionsAssignmentMixin, serializers.ModelSerializer):
type = models.CharField(max_length=50)
license = models.CharField(max_length=50)
class Meta:
model = CarRecord
fields = ('__all__')
def get_permissions_map(self, created):
current_user = self.context['request'].user
readers = Group.objects.get(name='readers')
editors = Group.objects.get(name='editors')
owners = Group.objects.get(name='owners')
return {
'view_car_record': [current_user, readers, owners],
'change_car_record': [current_user, editors],
'delete_car_record': [current_user, owners]
}
and your views should look like this:
from rest_framework_guardian import filters
class CarRecordModelViewSet(ModelViewSet):
queryset = CarRecord.objects.all()
serializer_class = CarRecordSerializer
filter_backends = [filters.ObjectPermissionsFilter]
Edit settings.py like this:
INSTALLED_APPS = [
'rest_framework',
'guardian',
]
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"guardian.backends.ObjectPermissionBackend",
]
You can define filter backends globally in your settings, too:
REST_FRAMEWORK = {
"DEFAULT_FILTER_BACKENDS": [
"django_filters.rest_framework.DjangoFilterBackend",
"rest_framework_guardian.filters.ObjectPermissionsFilter",
],
}
Don't forget! If you define the ObjectPermissionsFilter in the settings.py, your all views are affected by this filter.
If you want to restrict post request per user, you shoul implement custom permission class, like this:
from rest_framework import permissions
class CustomObjectPermissions(permissions.DjangoObjectPermissions):
"""
Similar to `DjangoObjectPermissions`, but adding 'view' permissions.
"""
perms_map = {
'GET': ['%(app_label)s.view_%(model_name)s'],
'OPTIONS': ['%(app_label)s.view_%(model_name)s'],
'HEAD': ['%(app_label)s.view_%(model_name)s'],
'POST': ['%(app_label)s.add_%(model_name)s'],
'PUT': ['%(app_label)s.change_%(model_name)s'],
'PATCH': ['%(app_label)s.change_%(model_name)s'],
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
}
Check this link to get the detailed information for the CustomObjectPermissions
You can write permission class car owner user.
Your model.
class CarRecord(models.Model):
date_created = models.DateTimeField()
type = models.CharField(max_length=50)
license = models.CharField(max_length=50)
owners = models.ManyToManyField(User, related_name='car_owners', verbose_name=('owners'), default=[]))
creator = models.ForeignKey(User,on_delete=models.DO_NOTHING)
Permission class permission.py
from rest_framework.permissions import BasePermission,
from cars.models import CarRecord
class isCarAccess(BaseCommand):
def has_permission(self, request, view):
if request.method == 'OPTIONS':
return True
check_user = CarRecord.objects.filter(owners__in=[request.user])
return request.user is not None and request.user.is_authenticated and check_user
this permission class will check that does user exists, user is authenticated and as well the user belongs to the card record or not.
And you can pass this permission in your view.
from .permission import isCarAccess
from .models import CarRecord
class CarRecordViews(APIView):
permission_classes = [isCarAccess]
def get(self, request):
car_record = CarRecord.objects.filter(owners__in=[request.user])
# return all records of cars that user some type of permission for
and your settings.py
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"oauth2_provider.contrib.rest_framework.OAuth2Authentication",
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.TokenAuthentication",
),
}
Hii I'am new to Django rest frame work and was preparing API's So this is my
models.py
class User(auth.models.User, auth.models.PermissionsMixin):
def __str__(self):
return "#{}".format(self.username)
class User_log(models.Model):
user = models.OneToOneField(auth.models.User,on_delete=models.CASCADE,related_name='user_logs')
fullname=models.CharField(max_length=255)
fb_login=models.BooleanField(default=False)
def __str__(self):
return self.fullname
serializers.py
class userSerializers(serializers.ModelSerializer):
fullname = serializers.StringRelatedField(source='user_logs.fullname',read_only=False)
fb=serializers.BooleanField(source='user_logs.fb_login')
class Meta:
model = User
fields=('id','username','email','fullname','fb')
related_fields = ['user_logs']
def update(self, instance, validated_data):
# Handle related objects
for related_obj_name in self.Meta.related_fields:
print('exe')
print(instance,validated_data)
# Validated data will show the nested structure
data = validated_data.pop(related_obj_name)
related_instance = getattr(instance, related_obj_name)
# Same as default update implementation
for attr_name, value in data.items():
setattr(related_instance, attr_name, value)
related_instance.save()
return super(userSerializers,self).update(instance, validated_data)
viewset.py
class Userview(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class=userSerializers
Now the problem: whenever i try to update fullname column through json request
{ "id": 2,"username": "karam","email": "karam#83ideas.com","fullname": "karm","fb": true } i am getting this error "POST /accounts/accounts/2/ HTTP/1.1" 405 41
and as in serializers.py update method i have printed the validated data instead of this
karam {'username': 'karam', 'email': 'karam#83ideas.com', 'user_logs': {'fullname':'karam','fb_login': True}}
i am getting this
karam {'username': 'karam', 'email': 'karam#83ideas.com', 'user_logs': {'fb_login': True}}
SO any idea how to resolve it?
Instead of using StringRelatedField in serializers.py use CharField.
class userSerializers(serializers.ModelSerializer):
fullname=serializers.CharField(source='user_logs.fullname')
fb=serializers.BooleanField(source='user_logs.fb_login')
class Meta:
model = User
fields=('id','username','email','fullname','fb')
related_fields = ['user_logs']
I am trying to incorporate inline add to my django form. The User can create a 'Site', within the 'Site' form the user can create multible 'Staff' to that 'Site'.
I have followed a tutorial which I believe to be the solution but can not get it to work.
Currently I am getting the error:
'Calling modelformset_factory without defining 'fields' or 'exclude' explicitly is prohibited.'
Here is my attempt.
models.py
class Site(models.Model):
...
class Staff(models.Model):
site = models.ForeignKey(Site)
....
views.py
class BaseNestedFormset(BaseInlineFormSet):
def add_fields(self, form, index):
# allow the super class to create the fields as usual
super(BaseNestedFormset, self).add_fields(form, index)
form.nested = self.nested_formset_class(
instance=form.instance,
data=form.data if self.is_bound else None,
prefix=' %s-%s' % (
form.prefix,
self.nested_formset_class.get_default_prefix(),
),
)
def is_valid(self):
result = super(BaseNestedFormset, self).is_valid()
if self.is_bound:
# look at any nested formsets, as well
for form in self.forms:
result = result and form.nested.is_valid()
return result
def save(self, commit=True):
result = super(BaseNestedFormset, self).save(commit=commit)
for form in self:
form.nested.save(commit=commit)
return result
def nested_formset_factory(site_model, staff_model):
parent_child = inlineformset_factory(
site_model,
staff_model,
formset=BaseNestedFormset,
)
parent_child.nested_formset_class = inlineformset_factory(
staff_model,
)
return parent_child
class SiteCreate(CreateView):
model = Site
form_class = SiteForm
queryset = Site.objects.all()
success_url = '/site/list'
def get_form_class(self):
return nested_formset_factory(
Site,
Staff,
)
forms.py
class SiteForm(forms.ModelForm):
class Meta:
model = Site
exclude = ('creation', 'last_modified')
def nested_formset_factory(site_model, staff_model):
parent_child = inlineformset_factory(
site_model,
staff_model,
formset=BaseNestedFormset,
fields = ('one', 'two', 'ect')
)
In relation to the question below, how would I do this for the django admin?
Add data to ModelForm object before saving
Example:
class FooInlineModel(models.Model):
bar = models.CharField()
secret = models.CharField()
class FooInlineForm(forms.ModelForm):
class Meta:
model = FooInlineModel
exclude = ('secret',)
class FooInline(admin.TabularInline):
pass
class FooAdmin(admin.ModelAdmin):
inlines = (FooInline,)
This logically triggers an IntegrityError because secret is submitted NULL, how do I manually populate the field in one of the admin methods? e.g.
class ???:
def ???:
obj = form.save(commit=False)
obj.secret = 'stackoverflow'
obj.save()
Alright I found the method, for those interested:
class FooAdmin(admin.ModelAdmin):
inlines = (FooInline,)
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
for instance in instances:
if isinstance(instance, FooInlineModel):
instance.secret = 'stackoverflow'
instance.save()