How can i correctly use add_form in admin panel when class is inherited from admin.ModelAdmin. I find out the hack with overriding get_form method where you can dynamically change form to add_form value. With current approach i'm getting this error
formsets, inline_instances = self._create_formsets(request, form.instance, change=False)
AttributeError: 'UserForm' object has no attribute 'instance'
form.py
class AddCustomProductForm(forms.Form):
users = forms.ChoiceField(
label='Select a Profile',
required=True,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["users"].choices = CustomUser.objects.all()
admin.py
class PostAdmin(admin.ModelAdmin):
list_display = ('email', 'created', 'company_id',)
add_form = AddCustomProductForm
form = CustomProductForm
fieldsets = (
(None, {"fields": ("users")}),
)
def get_form(self, request, obj=None, **kwargs):
defaults = {}
if obj is None:
defaults['form'] = self.add_form
defaults.update(kwargs)
return super().get_form(request, obj, **defaults)
You need a ModelForm, where you take the values directly from a Model( in this case ModelAdmin) in a form.
https://docs.djangoproject.com/en/4.0/topics/forms/modelforms/
The other way is make "your" admin panel.
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 created a CBV of which I want to remove one or more fields, depending on the user. The idea is a jobsite and if the logged in user is a recruiter, than the employer field should be included, otherwise it should be excluded.
forms.py
class JobCreationForm(forms.ModelForm):
class Meta:
model = Job
# exclude = ['posted', 'provider', 'ext_id']
fields = ('title',
'job_desc',
'agency_name',
'employer',
'contact_name',
)
views.py
class JobCreateView(LoginRequiredMixin, CreateView):
template_name = 'job/job.html'
form_class = JobCreationForm
success_url = '/'
def get_context_data(self, **kwargs):
context = super(JobCreateView, self).get_context_data(**kwargs)
# import the Customers of this Company
self.fields["agency_name"].remove()
recruiter = self.request.user
self.fields["contact_name"].queryset = Profile.objects.filter(user_id = self.request.user)
# if the user is a recruiter, delete the employer field.
if Company.objects.filter(user_id = self.request.user).values('is_recruiter') == False:
pass
# self.fields.remove("employer")
del self.fields["employer"]
return context
The current error is NoneType' object has no attribute '__getitem__'.
My question: how can I remove a field from the form based on logic? I tried these versions:
self.fields["employer"].delete()
self.fields.remove("employer")
del self.fields["employer"]
Any tips?
The correct way to implement this (modify the fields of the form depending on user) is to do it on your form's __init__ method. However in order for the form to access the current user you need to pass the user to it from your view. To do this you'll use the get_form_kwargs method. Thus, start by adding the following method to your view:
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
And now, you can add an __init__ to your form like this:
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
if Company.objects.filter(user_id = self.user).is_recruiter == False:
self.fields.pop("employer")
self.fields.pop('owned_by')
Notice that you first initialize the form (using super.__init__) and then you can modify the fields to your heart's content.
There are few ways to go about it.
I find having 2 separate forms RecruiterEmployeeForm and EmployeeForm may be neater.
class RecruiterEmployeeForm(forms.ModelForm):
model = Job
fields = ('title',
'job_desc',
'agency_name',
'employer',
'contact_name',
)
class EmployeeForm(forms.ModelForm):
model = Job
fields = ('title',
'job_desc',
'agency_name',
'contact_name',
)
Then you can override ger_form_class for the CBV
def get_form_class(self):
if self.request.user.is_recruiter():
return RecruiterEmployeeForm
else:
return EmployeeForm
To send extra kwargs to use generic view method get_form_kwargs and to get extra kwargs override __init__ of form and pop the extra kwargs.
forms.py
class JobCreationForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(JobCreationForm, self).__init__(*args, **kwargs)
if Company.objects.filter(user_id = self.user).is_recruiter == False:
self.fields.pop("employer")
class Meta:
model = Job
# exclude = ['posted', 'provider', 'ext_id']
fields = ('title', 'job_desc', 'agency_name', 'employer', 'contact_name')
views.py
class JobCreateView(LoginRequiredMixin, CreateView):
template_name = 'job/job.html'
form_class = JobCreationForm
success_url = '/'
def get_form_kwargs(self):
kwargs = super(JobCreateView, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
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
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