I have a model Employee with a OneToOne relationship with User. I'm trying to include in the Employee admin some User fields (first_name, last_name, username, email), in order to edit those fields directly from the Employee Add/Change form, but I still haven't figured out how to do it.
I see another time do this kind of thing in a similar way, but now I have a fieldset error:
Unknown field(s) (first_name) specified for Employee. Check fields/fieldsets/exclude attributes of class EmployeeAdmin
here's the code:
# FIELDSETS
default_employee_fieldset = (
('General', {
'fields': (
('user', 'full_name',),
),
}),
# ..OTHER FIELDS NOT INCLUDED
)
finance_fields_employee_fieldset = [
('General', {
'fields': (
('full_name',),
('first_name', 'last_name')
),
}),
# ..OTHER FIELDS NOT INCLUDED
]
# ADMIN FORM
class EmployeeAdminForm(forms.ModelForm):
class Meta:
model = Employee
def __init__(self, *args, **kwargs):
super(EmployeeAdminForm, self).__init__(*args, **kwargs)
self.fields['first_name'] = forms.CharField(_('first name'), max_length=30, blank=True)
self.fields['last_name'] = forms.CharField(_('last name'), max_length=30, blank=True)
if 'instance' in kwargs:
user = kwargs['instance'].user
self.fields['first_name'].initial = user.first_name
self.fields['last_name'].initial = user.last_name
# MODEL ADMIN
class EmployeeAdmin(ExtendedAdmin):
list_display = ('user', 'department', 'line_manager', 'acting_line_manager', 'jobtitle', 'office', 'payroll_id',)
search_fields = ('user__username', 'user__first_name', 'user__last_name',
'line_manager__user__first_name', 'line_manager__user__last_name')
list_filter = ('department', 'grade', 'clearance', 'office', 'user__is_active', 'user__is_staff',
'ready_for_paid_work')
fieldsets = default_employee_fieldset
def changelist_view(self, request, extra_context=None):
extra_context = {'title': 'Employee details'}
return super(EmployeeAdmin, self).changelist_view(request, extra_context)
def change_view(self, request, object_id, form_url='', extra_context=None):
if object_id:
try:
record = Employee.objects.get(id=object_id)
extra_context = {'title': 'Edit employee record: %s' % (str(record),)}
except (ValueError, Employee.DoesNotExist):
pass
# Generate a new CSRF token as this page contains sensitive data
rotate_token(request)
return super(EmployeeAdmin, self).change_view(request, object_id, form_url, extra_context)
def queryset(self, request):
qs = super(EmployeeAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
elif request.user.has_perm('myapp.change_all_employees'):
if request.user.has_perm('myapp.change_ex_employees'):
return qs.filter(user__is_staff=True)
else:
return qs.filter(user__is_staff=True, user__is_active=True)
else:
return qs.filter(user__is_staff=True, user__is_active=True).filter(
Q(line_manager=request.user.employee) |
Q(acting_line_manager=request.user.employee) |
Q(user=request.user)
)
def get_form(self, request, obj=None, *args, **kwargs):
self.form = EmployeeAdminForm
if obj:
if request.user.has_perm('myapp.change_hr_employee_data'):
self.readonly_fields = ('user', )
self.exclude = None
self.fieldsets = finance_fields_employee_fieldset
elif request.user.has_perm('myapp.change_employee_data_operations'):
if request.user == obj.user:
# set readonly fields
self.exclude = None
self.fieldsets = finance_fields_employee_fieldset
else:
# set readonly fields
self.exclude = ('employer_pension_contribution', 'employee_pension_contribution',)
self.fieldsets = default_employee_fieldset
else:
# if user is in AAA department and not employee's linemanager and not own record
if request.user.employee.department == settings.DPTS['AAA']
if request.user == obj.user:
# set readonly fields
else:
# set readonly fields
self.exclude = ('account_number', 'sort_code', 'salary',
'employer_pension_contribution', 'employee_pension_contribution',)
self.fieldsets = default_employee_fieldset
else:
# if user is editing his own record
if request.user == obj.user:
# set readonly fields
self.exclude = ('account_reference', 'payroll_id',)
self.fieldsets = finance_fields_employee_fieldset[0:-2]
# if user is editing some for whom is line manager
else:
if request.user.employee == obj.line_manager or \
request.user.employee == obj.acting_line_manager:
# set readonly fields
self.fieldsets = default_employee_fieldset
self.exclude = ('employer_pension_contribution', 'employee_pension_contribution',)
else:
self.readonly_fields = ()
self.exclude = ()
self.fieldsets = default_employee_fieldset
form = super(EmployeeAdmin, self).get_form(request, *args, **kwargs)
form.request = request
return form
def save_model(self, request, obj, form, change):
if change:
old_obj = Employee.objects.get(id=obj.id)
if obj.jobtitle != old_obj.jobtitle:
employee_detail_change_notification(obj, 'job title', obj.jobtitle.name)
if obj.line_manager != old_obj.line_manager:
employee_detail_change_notification(obj, 'line manager', obj.line_manager)
if obj.acting_line_manager != old_obj.acting_line_manager:
employee_detail_change_notification(obj, 'acting line manager', obj.acting_line_manager)
if obj.home_address_line1 != old_obj.home_address_line1 or \
obj.home_address_line2 != old_obj.home_address_line2 or\
obj.home_address_line3 != old_obj.home_address_line3 or\
obj.home_address_city != old_obj.home_address_city or\
obj.home_address_postcode != old_obj.home_address_postcode or\
obj.home_address_country != old_obj.home_address_country or\
obj.home_address_phone != old_obj.home_address_phone or\
obj.personal_email != old_obj.personal_email:
new_address = '%s\n%s\n%s\n%s\n%s\n%s\n\nPhone: %s\nEmail: %s' %(
obj.home_address_line1,
obj.home_address_line2,
obj.home_address_line3,
obj.home_address_city,
obj.home_address_postcode,
obj.get_home_address_country_display(),
obj.home_address_phone,
obj.personal_email,
)
employee_detail_change_notification(obj, 'home address details', new_address)
if obj.marital_status != old_obj.marital_status:
employee_detail_change_notification(obj, 'marital_status', obj.get_marital_status_display())
obj.save()
admin.site.register(Employee, EmployeeAdmin)
Any help on this?
The reason Admin doesn't find user_name is that the user_name field isn't defined on your custom form at class level. It is only defined in __init__. When the admin inspects the form for available fields it has access only to the class, not an initialized form. So inspecting the form class doesn't show that user_name is available.
Define all the fields directly on class level instead of the form's __init__ method. If you don't need all the fields for all cases, then delete fields as needed from the form's fields dictionary in the __init__ method as needed.
You could maybe also do this in the reverse direction - adding Employee information to User edit page? This can be done as documented in https://docs.djangoproject.com/en/1.4/topics/auth/#adding-userprofile-fields-to-the-admin.
Finally, it seems you are reaching the stage where it might be easier to implement the features you want without using Admin - while Django's Admin does have a lot of extensibility features, at some point writing your own implementation will be easier than using Admin.
Related
Using adminactions to my project I came up with the problem that my date widget(the calendar) is not appearing to my form at all.
I successfully install the django-admin-rangefilter and import it as well.
my model
class Order(models.Model):
stem_date = models.DateField("STEM")
delivery_date = models.DateField("Delivery Date")
my admin.py
from rangefilter.filter import DateRangeFilter, DateTimeRangeFilter
list_filter = [('stem_date', DateRangeFilter), ('delivery_date', DateRangeFilter), ('cost_center')]
I am using django version = 2.2.6 and python version 3.5.2
whole class concerning the order
class OrderAdmin(ReadPermissionModelAdmin):
### Default ordering with REF ###
ordering = ('-ref',)
#This Snippet is to order foreign key form fields
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "port":
kwargs["queryset"] = Port.objects.order_by('port_name')
if db_field.name == "customer":
kwargs["queryset"] = Customer.objects.order_by('customer_name')
if db_field.name == "contact":
kwargs["queryset"] = Contact.objects.order_by('surname')
return super(OrderAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
actions = [export_model_as_csv]
inlines = [Order_ProductsInline, Order_Product_ExtrasInline, Order_CommissionInline]
#Function to make ref id readonly after save
def get_readonly_fields(self, request, obj=None):
if obj: # when editing an object
return ['ref']
return self.readonly_fields
#sos
list_filter = [('stem_date', DateRangeFilter), ('delivery_date', DateRangeFilter), ('cost_center')]
#list_filter = [('stem_date', DateRangeFilter), ('delivery_date', DateRangeFilter)]
search_fields = ('customer__customer_name', 'vessel__ship_name', 'port__port_name', 'id', 'ref', 'supplier__customer_name')#if you look in foreign keys, must refferance with double underscore relevant field to other model
list_display = ['ref', 'customer', 'supplier', 'vessel', 'port', 'cost_center', 'delivery_date', 'show_details','cancelled',]
formfield_overrides = {
models.DecimalField: {'widget': TextInput(attrs={'size':'6'})},
models.BigIntegerField: {'widget': TextInput(attrs={'size':'6'})},
}
fieldsets = (
(None, {
'fields': (('ref'), ('customer', 'supplier'), ('vessel', 'imo', 'port', 'operator'), ('stem_date', 'delivery_date', 'contact'), 'cost_center', ('currency', 'bank_charges', 'branch'), ('cancelled'),('remarks'),)
}),
)
list_per_page = 20
#Function to show ID with 6 digits
def order_id(self, obj):
return str(obj.id).zfill(6)
def show_details(self, obj):
return_string = format_html('Details')
return return_string
show_details.allow_tags = True
show_details.short_description = 'Details'
def response_change(self, request, obj, post_url_continue=None):
#This makes the response go to inquiry full detail page for an edited order
return HttpResponseRedirect(reverse('orders', args=(obj.id,)))
#return HttpResponseRedirect("../../orders/%s" % obj.id)
def response_add(self, request, obj, post_url_continue=None):
#This makes the response go to inquiry full detail page for a newlly added order to inquiry list
return HttpResponseRedirect(reverse('orders', args=(obj.id,)))
#return HttpResponseRedirect("../../orders/%s" % obj.id)#strip leading zeros
admin.site.register(Order, OrderAdmin)
Have anyone any idea why my widget is not loaded at all?
Did you add rangefilter to your INSTALLED_APPS?
If so try like this:
from rangefilter.filter import DateRangeFilter
from .models import Order
#admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_filter = (
('stem_date', DateRangeFilter), ('delivery_date', DateRangeFilter)
)
I have tried all the different examples and methods I could find on Stack Overflow for this, and for whatever reason, can't get this to work properly.
So in my admin.py I have a UserForm and and UserAdmin. Based on the condition where a boolean is checked within the form, I want to change the 'required' attribute on a few different form fields to 'false' so I can effectively save. I've used a few different print statements to ascertain that the 'required' is in fact getting changed to false when the condition is met, however, when I try and save, it won't let me as the fields highlight and say they're still required.
It is almost like the save_model doesn't care how I edit the form, that the old form and its 'required' attributes are overriding my changes. Thanks for any help!
admin.py
class UserForm(forms.ModelForm):
state = forms.CharField(min_length=2, widget=forms.TextInput(attrs={'placeholder':'CA'}))
zipcode = forms.CharField(max_length=5, min_length=5,)
class Meta:
model = User
fields = '__all__'
class UserAdmin(admin.ModelAdmin):
model = Order
form = UserForm
def save_model(self, request, obj, form, change):
if obj.pickup_only == 1:
form.fields['address'].required = False
form.fields['city'].required = False
form.fields['state'].required = False
form.fields['zipcode'].required = False
return super(UserAdmin, self).save_model(request, obj, form, change)
revised code for UserForm:
def clean(self):
cleaned_data = super(UserForm, self).clean()
address = cleaned_data.get('address')
state = cleaned_data.get('state')
city = cleaned_data.get('city')
zipcode = cleaned_data.get('zipcode')
pickup_only = cleaned_data.get('pickup_only')
if pickup_only == True:
# I STUCK IN ADDRESS TO EMPHASIZE THE ERROR IN SCREENSHOT
self.fields_required(['first_name', 'last_name', 'dob', 'phone', 'email', 'address'])
else:
self.cleaned_data['address', 'city', 'state', 'zip code'] = ''
return self.cleaned_data
def fields_required(self, fields):
for field in fields:
print(field)
# RETURNS: first_name, last_name, dob, phone, email, address
if not self.cleaned_data.get(field, ''):
msg = forms.ValidationError("This field is required. Custom.")
self.add_error(field, msg)
When I save I get this:
You might have to tweak it a little bit to fit your needs, but you could try something like this in your clean method:
def clean(self):
cleaned_data = super(UserForm, self).clean()
state = cleaned_data.get('state')
zipcode = cleaned_data.get('zipcode')
pickup_only = cleaned_data.get('pickup_only')
if pickup_only == True:
self.fields_required(['any_fields_required',])
else:
self.cleaned_data['state', 'zip code'] = ''
return self.cleaned_data
def fields_required(self, fields):
for field in fields:
if not self.cleaned_data.get(field, ''):
msg = forms.ValidationError("This field is required.")
self.add_error(field, msg)
Also, as one of the commenters mentioned, you should not specify your model=Order in the UserAdmin. Your UserAdmin is for the User model. That isn't the proper way to add inlines. See below:
# You can also use (admin.TabularInline) depending on your needs.
class OrderInline(admin.StackedInline):
model = Order
list_display = ('order_fields',)
fieldsets = (
((''), {'fields': ('order_fields',)}),
)
class UserAdmin(admin.ModelAdmin):
model = User
inlines = [OrderInline,]
Add an init method to your form:
class UserForm(forms.ModelForm):
state = forms.CharField(min_length=2, widget=forms.TextInput(attrs={'placeholder':'CA'}))
zipcode = forms.CharField(max_length=5, min_length=5,)
class Meta:
model = User
fields = '__all__'
def __init__(self, *args, **kwargs):
super(UserForm, self).__init__(*args, **kwargs)
self.fields['address'].required = False
self.fields['city'].required = False
self.fields['state'].required = False
self.fields['zipcode'].required = False
def clean(self):
cleaned_data = super(UserForm, self).clean()
address = cleaned_data.get('address')
state = cleaned_data.get('state')
city = cleaned_data.get('city')
zipcode = cleaned_data.get('zipcode')
pickup_only = cleaned_data.get('pickup_only')
if pickup_only == True:
self.fields_required(['first_name', 'last_name', 'dob', 'phone', 'email', 'address'])
else:
self.cleaned_data['address', 'city', 'state', 'zip code'] = ''
return self.cleaned_data
def fields_required(self, fields):
for field in fields:
if not self.cleaned_data.get(field, ''):
msg = forms.ValidationError("This field is required. Custom.")
self.add_error(field, msg)
Try this. If it works then I don't think you have to override the save method in your UserAdmin. I would avoid that anyway unless absolutely necessary.
I have a admin master detail, using tabular inline forms.
I have some special validations to accomplish:
If "field_type" is "list", validate that at least one item in the formset has been added.
But if not (field_type has another value), do not validate.
If "field_type" is "list", then, make visible the formset, otherwise hide it. This is javascript. I also must validate that on the server. I do that on the clean() of ValueItemInlineFormSet. The problem is that is now validating formset always and it should just happen when field_type = "list". How can I get the value of my master field into the formset?
class ValueItemInlineFormSet(BaseInlineFormSet):
def clean(self):
"""Check that at least one service has been entered."""
super(ValueItemInlineFormSet, self).clean()
if any(self.errors):
return
print(self.cleaned_data)
if not any(cleaned_data and not cleaned_data.get('DELETE', False) for cleaned_data in self.cleaned_data):
raise forms.ValidationError('At least one item required.')
class ValueItemInline(admin.TabularInline):
model = ValueItem
formset = ValueItemInlineFormSet
class MySelect(forms.Select):
def render_option(self, selected_choices, option_value, option_label):
if option_value is None:
option_value = ''
option_value = force_text(option_value)
data_attrs = self.attrs['data_attrs']
option_attrs = ''
if data_attrs and option_value:
obj = self.choices.queryset.get(id=option_value)
for attr in data_attrs:
attr_value = getattr(obj, attr)
option_attrs += 'data-{}={} '.format(attr, attr_value)
if option_value in selected_choices:
selected_html = mark_safe(' selected="selected"')
if not self.allow_multiple_selected:
# Only allow for a single selection.
selected_choices.remove(option_value)
else:
selected_html = ''
return format_html('<option value="{}" {}{}>{}</option>', option_value, option_attrs, selected_html, force_text(option_label))
class RequirementFieldForm(forms.ModelForm):
field_type = forms.ModelChoiceField(queryset=FieldType.objects.all(),
widget=MySelect(attrs={'data_attrs': ('identifier', 'name')}))
def __init__(self, *args, **kwargs):
self.qs = FieldType.objects.all()
super(RequirementFieldForm, self).__init__(*args, **kwargs)
class Meta:
model = RequirementField
fields = ['field_type', 'name', 'description', 'identifier', 'mandatory', 'order_nr', 'active']
I'm trying to run a validation where a user can't enter the same name_field twice but other users entering the same name will not interfere.
I tried using "unique_together = (("username","name_field"))" but when a user enters the same value twice the server generates an integrity error as opposed to rendering a warning message next to the form field.
then I tried overriding the clean() method in my model, Which runs fine if I only check "field_name" like so:
def clean(self):
existing = self.__class__.objects.filter(
name_field=self.name_field).count()
if existing > 0:
raise ValidationError({'name_field':self.username })
But I am running into trouble when checking the username value, for instance:
def clean(self):
existing = self.__class__.objects.filter(
username=self.username, ###This part crashes!!! (username not found)
name_field=self.name_field).count()
if existing > 0:
raise ValidationError({'name_field':self.username })
I'm guessing due to it not being an actual field in the form its not present during the call to clean(). So my question is am I doing the validation correctly for this kind of problem? And how can I pass or where can I find the value for the current user from within a models clean method (in a safe way hopefully without adding fields to my form)?
views.py
def add_stuff(request):
if request.user.is_authenticated():
form = addStuff(request.POST or None)
if request.method == 'POST':
if form.is_valid():
sub_form = form.save(commit=False)
sub_form.username = request.user
sub_form.save()
return redirect('../somewhere_else/')
forms.py
class addStuff(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(addStuff, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
'name_field',
'type_field',
ButtonHolder(
Submit('Save', 'Save')
),
)
class Meta:
model = UserStuff
fields = ('name_field',
'type_field',
)
models.py
class UserStuff(models.Model):
username = models.ForeignKey(User)
name_field = models.CharField(max_length=24, blank=False,null=False)
type_field = models.CharField(max_length=24, blank=True,null=True)
def clean(self):
existing = self.__class__.objects.filter(
username=self.username, ###This part crashes!!! (username not found)
name_field=self.name_field).count()
if existing > 0:
raise ValidationError({'name_field':self.username })
def __unicode__(self):
return "%s For User: \"%s\" " % (self.name_field, self.username)
class Meta:
managed = True
db_table = 'my_db_table'
unique_together = (("username","name_field"))
Thanks for any insight!
I now am running the clean override from the form instead of the model (as recommended by Daniel). This has solved a bunch of issues and I now have a working concept:
models.py
class UserStuff(models.Model):
username = models.ForeignKey(User)
name_field = models.CharField(max_length=24, blank=False,null=False)
type_field = models.CharField(max_length=24, blank=True,null=True)
def clean(self):
existing = self.__class__.objects.filter(
username=self.username, ###This part crashes!!! (username not found)
name_field=self.name_field).count()
if existing > 0:
raise ValidationError({'name_field':self.username })
def __unicode__(self):
return "%s For User: \"%s\" " % (self.name_field, self.username)
class Meta:
managed = True
db_table = 'my_db_table'
forms.py
class addStuff(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(addStuff, self).__init__(*args, **kwargs)
initial = kwargs.pop('initial')
self.username = initial['user']
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
'name_field',
'type_field',
ButtonHolder(
Submit('Save', 'Save')
),
)
def clean(self):
cleaned_data = super(addStuff, self).clean()
name_field = self.cleaned_data['name_field']
obj = UserStuff.objects.filter(username_id=self.username.id,
name_field=name_field,
)
if len(obj) > 0:
raise ValidationError({'name_field':
"This name already exists!" } )
return cleaned_data
class Meta:
model = UserStuff
fields = ('name_field',
'type_field',
)
views.py
def add_stuff(request):
if request.user.is_authenticated():
form = addStuff(request.POST or None,
initial={'user':request.user})
if request.method == 'POST':
if form.is_valid():
sub_form = form.save(commit=False)
sub_form.username = request.user
sub_form.save()
return redirect('../somewhere_else/')
best of luck!
My admin looks like this (with no exclude variable):
class MovieAdmin(models.ModelAdmin)
fields = ('name', 'slug', 'imdb_link', 'start', 'finish', 'added_by')
list_display = ('name', 'finish', 'added_by')
list_filter = ('finish',)
ordering = ('-finish',)
prepopulated_fields = {'slug': ('name',)}
form = MovieAdminForm
def get_form(self, request, obj=None, **kwargs):
form = super(MovieAdmin, self).get_form(request, obj, **kwargs)
form.current_user = request.user
return form
admin.site.register(Movie, MovieAdmin)
The form:
class MovieAdminForm(forms.ModelForm):
class Meta:
model = Movie
def save(self, commit=False):
instance = super(MovieAdminForm, self).save(commit=commit)
if not instance.pk and not self.current_user.is_superuser:
if not self.current_user.profile.is_manager:
instance.added_by = self.current_user.profile
instance.save()
return instance
I'm trying to remove the added_by field for users since I'd prefer to populate that from the session. I've tried methods from the following:
Django admin - remove field if editing an object
Remove fields from ModelForm
http://www.mdgart.com/2010/04/08/django-admin-how-to-hide-fields-in-a-form-for-certain-users-that-are-not-superusers/
However with each one I get: KeyError while rendering: Key 'added_by' not found in Form. It seems I need to remove the field earlier in the form rendering process but I'm stuck on where to do this.
So how can I exclude the added_by field for normal users?
You're probably getting that error when list_display is evaluated. You can't show a field that's excluded. The version with added_by removed also needs a corresponding list_display.
def get_form(self, request, obj=None, **kwargs):
current_user = request.user
if not current_user.profile.is_manager:
self.exclude = ('added_by',)
self.list_display = ('name', 'finish')
form = super(MovieAdmin, self).get_form(request, obj, **kwargs)
form.current_user = current_user
return form