restrict django choice field on django admin per user - python

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

Related

Not able to get current logged in user in django model

I am trying to get current logged in user through my model so that I can only see the current user in my order page dropdown:
I have gone through a lot of documents which state that it is not that easy or feasible to get current logged in user in model.
I have tried other method like getting AUTH_USER_MODEL but it is returning admin level users as well so not solving the problem.
I am also sending the current logged in user from my views file but dont know how to access it inside form class, able to access it in init but dont know how it can be accessed in class.
models.py :
from django.db import models
from django.contrib.auth.models import User
from django.conf import settings
from django.contrib.auth import get_user_model
from django.http import HttpResponse,HttpRequest
class Customer(models.Model):
name = models.CharField(max_length=200,null=True)
user = models.OneToOneField(User, null=True, on_delete=models.CASCADE)
date_created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Order(models.Model):
product = models.ForeignKey(Product,null=True, on_delete=models.SET_NULL)
#customer = models.ForeignKey(settings.AUTH_USER_MODEL,null=True, on_delete=models.SET_NULL)
customer = models.ForeignKey(Customer,null=True, on_delete=models.SET_NULL)
date_ordered = models.DateTimeField(auto_now_add=True)
status = models.CharField(max_length=20,default= 'PENDING')
def __str__(self):
return str(self.customer.id)
forms.py :
class createorderform(ModelForm):
def __init__(self,*args,**kwargs):
self._instance=kwargs.pop('instance',None)
super().__init__(*args,**kwargs)
#def __init__(self,instance):
# self.user = instance
# super().__init__(instance)
class Meta:
model=Order
fields="__all__"
exclude=['status']
Views.py
def placeorder(request,i):
try:
products = Product.objects.all()
customer = Customer.objects.get(id=i)
print("Customer:",customer)
#form=createorderform(prod=products,cust=customer)
form=createorderform(instance=customer)
#form=createorderform()
if(request.method=='POST'):
form=createorderform(request.POST,instance=customer)
if(form.is_valid()):
form.save()
return redirect('/')
context={'form':form}
return render(request,'app_name/placeorder.html',context)
except:
print("Error occurred : {exec}".format(exec=traceback.format_exc()))
What I am getting is all the users:
What I want is to only show the current user in drop down.
Please help or guide me in the right direction.
Thanks in advance!!
Something like
from django.forms import ModelForm
from django.views.generic import CreateView
class CreateOrderForm(ModelForm):
class Meta:
model = Order
exclude = ['status', 'customer']
def __init__(self, **kwargs):
self.customer = kwargs.pop('customer')
super().__init__(**kwargs)
def save(self, commit=True):
self.instance.customer = self.customer
return super().save(commit=commit)
class PlaceOrderView(CreateView):
model = Order
form_class = CreateOrderForm
template_name = 'app_name/placeorder.html'
def get_form_kwargs(self):
return {
**super().get_form_kwargs(),
'customer': Customer.objects.get(user=self.request.user),
}
should be enough.
So I have kind of tried one solution and this one work, although I have tried it earlier but in between 100's of documents and solutions the easy one got lost :
In my forms.py :
class createorderform(ModelForm):
def __init__(self,*args,**kwargs):
self._instance=kwargs.pop('instance',None)
super().__init__(*args,**kwargs)
#def __init__(self,instance):
# self.user = instance
# super().__init__(instance)
class Meta:
model=Order
fields="__all__"
exclude=['customer','status']
I excluded the customer field and instead populated it in my views. py:
def placeorder(request,i):
try:
products = Product.objects.all()
customer = Customer.objects.get(id=i)
print("Customer:",customer)
#form=createorderform(prod=products,cust=customer)
form=createorderform(instance=customer)
#form=createorderform()
if(request.method=='POST'):
form=createorderform(request.POST,instance=customer)
if(form.is_valid()):
curr_user = form.save(commit=False)
curr_user.customer = customer
curr_user.save()
return redirect('/')
context={'form':form}
return render(request,'app_name/placeorder.html',context)
except:
print("Error occurred : {exec}".format(exec=traceback.format_exc()))
So I am overriding the save() and building my current logged in user in the view itself while saving it. It's storing the same user in db as well.
EDIT:
def placeorder(request,i):
try:
products = Product.objects.all()
customer = Customer.objects.get(id=i)
print("Customer:",customer)
#form=createorderform(prod=products,cust=customer)
form=createorderform()
#form=createorderform(instance=customer)
if(request.method=='POST'):
form=createorderform(request.POST)
#form=createorderform(request.POST,instance=customer)
if(form.is_valid()):
curr_user = form.save(commit=False)
curr_user.customer = customer
curr_user.save()
return redirect('/')
context={'form':form}
return render(request,'app_name/placeorder.html',context)
except:
print("Error occurred : {exec}".format(exec=traceback.format_exc()))
So I have change my form object and I am not sending my current logged in user to forms any more so just creating the object without "instance=customer" which got my current logged in user.
Now we don't need to handle the instance in init method of createorderform form :
class createorderform(ModelForm):
#def __init__(self,*args,**kwargs):
# self._instance=kwargs.pop('instance',None)
# super().__init__(*args,**kwargs)
#def __init__(self,instance):
# self.user = instance
# super().__init__(instance)
class Meta:
model=Order
fields="__all__"
exclude=['customer','status']

ViewSet and additional retrieve URL

I have a django model Donation that I expose as a ViewSet. Now I want to add an additional URL to a second model Shop where a related instance of Donation can be retrieved via the parameter order_id and custom actions can be executed.
# models.py
class Donation(models.Model):
id = models.AutoField(primary_key=True)
order_id = models.StringField(help_text='Only unique in combination with field `origin`')
origin = models.ForeignKey('Shop', on_delete=models.PROTECT)
class Shop(models.Model):
id = models.AutoField(primary_key=True)
# views.py
class DonationViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet):
def retrieve(self, request, *args, **kwargs):
if kwargs['pk'].isdigit():
return super(DonationViewSet, self).retrieve(request, *args, **kwargs)
else:
shop_id = self.request.query_params.get('shop_id', None)
order_id = self.request.query_params.get('order_id', None)
if shop_id is not None and order_id is not None:
instance = Donations.objects.filter(origin=shop_id, order_id=order_id).first()
if instance is None:
return Response(status=status.HTTP_404_NOT_FOUND)
return Response(self.get_serializer(instance).data)
return Response(status=status.HTTP_404_NOT_FOUND)
#action(methods=['post'], detail=True)
def custom_action(self, request, *args, **kwargs):
pass
class ShopViewSet(viewsets.ModelViewSet):
pass
# urls.py
router = routers.DefaultRouter()
router.register(r'donations', DonationViewSet)
router.register(r'shops', ShopViewSet)
router.register(r'shops/(?P<shop_id>[0-9]+)/donations/(?P<order_id>[0-9]+)', DonationViewSet)
My goal is to have http://localhost:8000/donations point at the entire DonationViewSet. Also I would like to lookup an individual donation, by its combination of shop_id and order_id like follows http://localhost:8000/shops/123/donations/1337/ and also executing the custom action like follows http://localhost:8000/shops/123/donations/1337/custom_action/. The problem I have is that the second url returns an entire queryset, not just a single instance of the model.
You can also use drf-nested-routers, which will have something like this:
from rest_framework_nested import routers
from django.conf.urls import url
# urls.py
router = routers.SimpleRouter()
router.register(r'donations', DonationViewSet, basename='donations')
router.register(r'shops', ShopViewSet, basename='shops')
shop_donations_router = routers.NestedSimpleRouter(router, r'', lookup='shops')
shop_donations_router.register(
r'donations', ShopViewSet, basename='shop-donations'
)
# views.py
class ShopViewSet(viewsets.ModelViewSet):
def retrieve(self, request, pk=None, donations_pk=None):
# pk for shops, donations_pk for donations
#action(detail=True, methods=['PUT'])
def custom_action(self, request, pk=None, donations_pk=None):
# pk for shops, donations_pk for donations
This is not tested! But in addition to what you already have, this will support:
donations/
donations/1337/
shops/123/donations/1337/
shops/123/donations/1337/custom_action
You can add urls by simply appending to the router's urls in the config like so. If all you want to do is add a single action from a view for one specifc url, and dont need all of the actions/urls for the viewset
# urls.py
urlpatterns = [
path('some_path', my_lookup_view)),
] # typical django url convention
urlpatterns += router.urls
# views.py
#api_view(['POST'])
def my_looup_view(request, shop_id, order_id):
# ...some lookup...
pass
You'll want to
derive from GenericViewSet since you're using models anyway
override get_object() instead with your custom lookup logic:
from rest_framework import mixins
from rest_framework.generics import get_object_or_404
from rest_framework.viewsets import GenericViewSet
class MyModelViewSet(
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.ListModelMixin,
GenericViewSet,
):
def get_object(self):
queryset = self.filter_queryset(self.get_queryset())
lookup_value = self.kwargs["pk"]
if lookup_value.isdigit():
obj = get_object_or_404(queryset, pk=lookup_value)
else:
obj = get_object_or_404(queryset, third_party_service_id=lookup_value)
self.check_object_permissions(self.request, obj)
return obj
#action(methods=["post"], detail=True)
def custom_action(self, request, *args, **kwargs):
thing = self.get_object()
# urls.py
router.register(r"mymodels", MyModelViewSet)
This should let you do
mymodels/ to list models,
mymodels/123/ to get a model by PK,
mymodels/kasjdfg/ to get a model by third-party service id,
mymodels/123/custom_action/ to run custom_action on a model by PK
mymodels/kasjdfg/custom_action/ to run custom_action on a model by service id,

Django import-export how to add current admin user to model

I am trying to make import-export select current/save user(defined as "author" field in model)
I tried this(save_model) but it doesn't work(I think because of resource class)
class KWResource(resources.ModelResource):
class Meta:
model = KW
import_id_fields = ('Keyword',)
fields = ('Keyword',)
searchess = fields.Field(attribute='searches', column_name="Avg. Monthly Searches (exact match only)")
compp = fields.Field(attribute='comp', column_name="Competition")
cpcc = fields.Field(attribute='cpc', column_name="Suggested bid")
def save_model(self, request, obj, form, change):
if getattr(obj, 'author', None) is None:
obj.author = request.user
obj.save()
How to get id of admin user that initiated import here?
Figured it out, not sure if it's the best solution but it works:
class KWResource(resources.ModelResource):
....
....
def before_import(self, dataset, dry_run, *args, **kwargs):
li = []
li.append(kwargs.pop('user', 1))
dataset.insert_col(0, li, 'user')
return super(KWResource, self).before_import(dataset, dry_run, *args, **kwargs)
For anyone finding this later, you can access the current django user in a hook on your Resource. There's a 'user' in the **kwargs.
def before_import_row(self, row, **kwargs):
row['author'] = kwargs['user'].id
More detail here:
django-import-export assign current user

add data to admin modelform inline object before saving

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()

admin for editing particular user

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)

Categories