Wish to filter a dropdown list based on the currently logged in user in the model admin by overriding the changelist view. I am trying to filter the dropdown of categories belonging to the user of the department only
class CategoryForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(CategoryForm, self).__init__(*args, **kwargs)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['department','name']
list_filter = ['department','name']
def changelist_view(self, request, extra_context=None):
extra_context = extra_context or {}
self.form = CategoryForm
print dir(self.form)
self.form.fields['department'].queryset = Department.objects.filter(
name = request.user.customuser.department.name)
How can this be achieved? Using django 1.6.5
ERROR
type object 'CategoryForm' has no attribute 'fields'
In general it is not a goot practice to alter base_fields of ModelForm class, but in this case class is generated on every request so it is OK.
#admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(CategoryAdmin, self).get_form(request, obj=None, **kwargs)
form.base_fields['department'].queryset = Department.objects.filter(
name=request.user.department.name)
return form
Related
I am using python 3.10 and Django 4.1
The model I'm making an admin panel for has a JSONField that will hold values from dynamically created form fields in the Admin panel. The issue is that I should be able to also dynamically set them to readonly from the AdminModel using the get_readonly_fields method.
To do that I made a custom ModelForm which adds new fields inside the _init_ method.
class CustomForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(CustomForm, self).__init__(*args, **kwargs)
for name in dynamic_fields_names():
self.fields[name] = forms.ChoiceField(choices=dynamic_fields_values(name))
def clean(self):
cleaned_data = super(ExecutionAdminForm, self).clean()
for name in dynamic_fields_names():
self.instance.description_fields[name] = cleaned_data.pop(name)
class Meta:
model = Execution
fields = "__all__"
#admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
form = CustomForm
def get_form(self, request, obj=None, **kwargs):
kwargs["fields"] = admin.utils.flatten_fieldsets(self.fieldsets)
return super(MyModelAdmin, self).get_form(request, obj, **kwargs)
def get_fieldsets(self, request, obj):
return (
(None, {"fields": ("user", "status")}),
("Description", {"fields": dynamic_fields_names()})
)
def get_readonly_fields(self, request, obj):
if can_change_dynamic_fields:
return []
return dynamic_fields_names()
When I set them to readonly using get_readonly_fields they are rendered as empty inside the browser
Not set as readonly
Set as readonly
I'm creating a form that will change the state of reserve book, I have this
class LibraryReserveForm(CrispyFormMixin, forms.Form):
def __init__(self, *args, **kwargs):
self.manager = kwargs.pop('manager')
super(LibraryReserveForm, self).__init__(*args, **kwargs)
def save(self):
self.instance.reserve_status = 'approved'
self.instance.save()
return self.manager
models.py
class ReservedBooks(TimeStampedModel):
BOOK_RESERVE_STATUS = Choices(
('for_approval', "For Approval"),
('approve', "Approved"),
('cancelled', "Cancelled"),
('rejected', "Rejected")
)
reserve_status = models.CharField(
_('Status'),
max_length=32,
choices=BOOK_RESERVE_STATUS,
default='for_approval'
)
...
view
class LibraryReserveView(
ProfileTypeRequiredMixin,
MultiplePermissionsRequiredMixin,
FormView,
):
model = ReservedBooks
template_name = 'library/reserved_list.html'
form_class = LibraryReserveForm
def get_form_kwargs(self):
kwargs = super(LibraryReserveView, self).get_form_kwargs()
kwargs.update({
'manager': self.request.user.manager,
})
return kwargs
urls
url(
r'^reserve/(?P<pk>\d+)/$',
views.LibraryReserveView.as_view(),
name='reserved'
),
everytime I submit the button I print something in the save() method of the forms but its not printing something therefore that method is not called. How do you called the save method ? Thanks
A FormView does not handle saving the object. It simply calls form_valid that will redirect to the success_url. But an UpdateView adds boilerplate code to pass the instance to the form, and will save the form.
You work with a Form, but a Form has no .instance attribute. A ModelForm has, so it might be better to use a ModelForm here:
class LibraryReserveForm(CrispyFormMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
self.manager = kwargs.pop('manager')
super(LibraryReserveForm, self).__init__(*args, **kwargs)
def save(self, *args, **kwargs):
self.instance.reserve_status = 'approved'
return super().save(*args, **kwargs)
Then we can make use of an UpdateView:
from django.views.generic import UpdateView
class LibraryReserveView(
ProfileTypeRequiredMixin,
MultiplePermissionsRequiredMixin,
UpdateView
):
model = ReservedBooks
template_name = 'library/reserved_list.html'
form_class = LibraryReserveForm
success_url = …
def get_form_kwargs(self):
kwargs = super(LibraryReserveView, self).get_form_kwargs()
kwargs.update(
manager=self.request.user.manager
)
return kwargs
You still need to specify the sucess_url here: the URL to which a successful POST request will redirect to implement the Post/Redirect/Get pattern [wiki].
I have a DemandDetailView(DetailView) and BidCreationView(CreateView).
On DemandDetailView page, there is a form (for creating Bids) which posts data to BidCreationView.
I can't figure out what to do in case form is invalid. I would like to render DemandDetailView again with form errors and preserve corresponding URL.
class DemandDetailView(DetailView):
model = Demand
template_name = 'demands/detail.html'
def dispatch(self, request, *args, **kwargs):
self.bid_creation_form = BidCreationForm(request.POST or None, request.FILES or None,request=request)
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['bid_creation_form']=self.bid_creation_form
return context
class BidCreationView(CreateView):
http_method_names = ['post']
model = Bid
form_class = BidCreationForm
def get_success_url(self):
return reverse_lazy("demands:demands")
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({'request': self.request})
return kwargs
def form_valid(self, form):
form.instance.demand_id = self.kwargs.pop('demand_id')
return super().form_valid(form)
Do you have any ideas? My only idea is to use session which isn't probably the best way.
You could use is_valid() method from Form Objects. Something like:
class DemandDetailView(DetailView):
model = Demand
template_name = 'demands/detail.html'
def dispatch(self, request, *args, **kwargs):
form = BidCreationForm(request.POST or None, request.FILES or None,request=request)
if form.is_valid():
self.bid_creation_form = form
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['bid_creation_form']=self.bid_creation_form
return context
Option #2 (Personal Choice):
forms.py
from django import forms
from .models import Bid
class BidCreationForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(BidCreationForm, self).__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs = {"class": "form-control"}
class Meta:
model = Bid
fields = ('user', 'demands', 'amount', 'transaction')
Take a look at the Meta class within the Form. Its explicitly calling the Bid Model and the fields attribute is referring Bid fields from the Model Instance. Now you could call this form in any view without calling another view. If you want to add logic to this form, like calculating total amount or something like that then should do it within the form also. Write code once, Dont Repeat yourself.
I have extended the admin model, so for each staff member i can assign other customers only if they are in the same group.
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
is_manager = models.BooleanField(default=False)
group_account = models.CharField(max_length=3,blank=True,null=True)
clients_assigned = models.ManyToManyField(User, limit_choices_to = Q(groups__groupaccount__group_account=group_account),blank=True,null=True,related_name='+')
class UserProfileInline(admin.StackedInline):
model = UserProfile
verbose_name = "User extra"
verbose_name_plural = "extra"
filter_horizontal = ('clients_assigned', )
def save_model(self, request, obj, form, change):
return super().save_model(request, obj, form, change)
class UserAdmin(BaseUserAdmin):
inlines = [UserProfileInline, ]
def get_form(self, request, obj=None, **kwargs):
#permissions reduce for regular staff
if (not request.user.is_superuser):
self.exclude = ("app")
self.exclude = ("user_permissions")
## Dynamically overriding
#self.fieldsets[2][1]["fields"] = ('is_active', 'is_staff','is_superuser','groups')
self.fieldsets[2][1]["fields"] = ('is_active', 'is_staff')
form = super(UserAdmin,self).get_form(request, obj, **kwargs)
return form
and extended the group admin model
class GroupAccount(models.Model):
group_account = models.CharField(,max_length=3,blank=True,null=True)
group = models.OneToOneField(Group,blank=True,null=True)
def save(self, *args, **kwargs):
super(GroupAccount, self).save(*args, **kwargs)
what im trying to do is simply to limit the client list for each manager-user by his and their group indicator(group_account field), means the available client list are those whom have the same specific group as himself, such as '555'
when the group_account = '555' in the DB the result of groups__groupaccount__group_account=group_account are empty
but if i change it Hardcoded to: groups__groupaccount__group_account='555'
its return the relevant result.
is that possible and/or what the alternative?
django 1.9
thanks for the help
You should custom the formfield_for_foreignkey method of StackInline
class UserProfileInline(admin.StackedInline):
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
field = super(UserProfileInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
if db_field.name == 'clients_assigned':
u = request.user
if not u.is_superuser:
field.queryset = field.queryset.filter(groups__in=u.groups.all())
return field
I have these models:
class Entity(models.Model):
name=models.CharField(max_length=100)
class Theme(models.Model):
name=models.CharField(max_length=100)
entity=models.OneToOneField(Entity)
class Company(models.Model):
name=models.CharField(max_length=100)
theme=models.OneToOneField(Theme,null=True,blank=True)
I want to filter the theme field when adding a Company in the admin, something like this:
class CompanyAdmin(admin.ModelAdmin):
def queryset(self, request):
qs = super(CompanyAdmin, self).queryset(request)
qs.theme.queryset = Theme.objects.filter(name__iexact='company')
return qs
admin.site.register(Company, CompanyAdmin)
I've tried many things, but nothing worked! How can I do this?
Use the render_change_form method:
class CompanyAdmin(admin.ModelAdmin):
def render_change_form(self, request, context, *args, **kwargs):
context['adminform'].form.fields['theme'].queryset = Theme.objects.filter(name__iexact='company')
return super(CompanyAdmin, self).render_change_form(request, context, *args, **kwargs)
I actually prefer to do it in get_form like so:
Django < 2:
class CompanyAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(CompanyAdmin, self).get_form(request, obj, **kwargs)
form.fields['theme'].queryset = Theme.objects.filter(name__iexact='company')
return form
Django >= 2
class CompanyAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(CompanyAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['theme'].queryset = Theme.objects.filter(name__iexact='company')
return form
look here http://books.agiliq.com/projects/django-admin-cookbook/en/latest/filter_fk_dropdown.html
#admin.register(Hero)
class HeroAdmin(admin.ModelAdmin, ExportCsvMixin):
...
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "category":
kwargs["queryset"] = Category.objects.filter(name__in=['God', 'Demi God'])
return super().formfield_for_foreignkey(db_field, request, **kwargs)
In Django 3 it is easy :
class CompanyAdmin(admin.ModelAdmin):
list_display = ('name','theme')
list_filter = ('theme__name',)
admin.site.register(Company,CompanyAdmin)
This will show you a filter on the right of your screen with the list of your theme's name.
Another option is to create a custom model form where the queryset attribute of the theme field will be fine tuned to meet your needs.
class CompanyForm(ModelForm):
class Meta:
model = CompanyForm
fields = __all__ # or a tuple of fields
def __init__(self, *args, **kwargs):
super(CompanyForm, self).__init__(*args, **kwargs)
if self.instance: # Editing and existing instance
self.fields['theme'].queryset = Theme.objects.filter(name__iexact='company')
This model form can be also reused outside of the django admin area.
I faced with the need to add filter to the foreignKey queryset of the parent ModelAdmin class (which all other ModelAdmins inherit from), that is, I can’t know exactly which model I need, this is my solution: db_field.related_model.objects.filter()
class TSModelAdmin(admin.ModelAdmin):
exclude = ('site',)
...
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.related_model:
kwargs["queryset"] =
db_field.related_model.objects.filter(site=request.user.user_profile.site)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
used django version 2.2.10
A bit unrelated, but similar to this so I'll post this here.
I was looking for a way to remove the NULL choice selection on a ModelForm foreignkey field. I first thought I could filter the queryset as is done in other answers here, but that didn't work.
I found that I can filter the entry where the pk value is NULL like this in the get_form method:
class CompanyAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
# remove null choice
form.base_fields["theme"].choices = ((pk, display) for pk, display in form.base_fields["theme"].choices if pk)
return form