34đź‘Ť
I took Carl’s advice and made a much better implementation then the hack-ish one I mentioned in my comment to his answer. Here is my solution:
From my forms.py:
from django.forms.models import BaseInlineFormSet
class RequiredInlineFormSet(BaseInlineFormSet):
"""
Generates an inline formset that is required
"""
def _construct_form(self, i, **kwargs):
"""
Override the method to change the form attribute empty_permitted
"""
form = super(RequiredInlineFormSet, self)._construct_form(i, **kwargs)
form.empty_permitted = False
return form
And the admin.py
class ProfileInline(admin.StackedInline):
"""
Allows profile to be added when creating user
"""
model = Profile
extra = 1
max_num = 1
formset = RequiredInlineFormSet
class UserProfileAdmin(admin.ModelAdmin):
"""
Options for the admin interface
"""
inlines = [ProfileInline]
list_display = ['edit_obj', 'name', 'username', 'email', 'is_active',
'last_login', 'delete_obj']
list_display_links = ['username']
list_filter = ['is_active']
fieldsets = (
(None, {
'fields': ('first_name', 'last_name', 'email', 'username',
'is_active', 'is_superuser')}),
(('Groups'), {'fields': ('groups', )}),
)
ordering = ['last_name', 'first_name']
search_fields = ['first_name', 'last_name']
admin.site.register(User, UserProfileAdmin)
This does exactly what I want, it makes the Profile inline formset validate. So since there are required fields in the profile form it will validate and fail if the required information isn’t entered on the inline form.
35đź‘Ť
Now with Django 1.7 you can use parameter min_num
. You do not need class RequiredInlineFormSet
anymore.
class ProfileInline(admin.StackedInline):
"""
Allows profile to be added when creating user
"""
model = Profile
extra = 1
max_num = 1
min_num = 1 # new in Django 1.7
class UserProfileAdmin(admin.ModelAdmin):
"""
Options for the admin interface
"""
inlines = [ProfileInline]
...
admin.site.register(User, UserProfileAdmin)
- [Django]-Django Models (1054, "Unknown column in 'field list'")
- [Django]-Django: Parse JSON in my template using Javascript
- [Django]-Django REST Framework – 405 METHOD NOT ALLOWED using SimpleRouter
10đź‘Ť
You can probably do this, but you’ll have to get your hands dirty in the formset/inline code.
First of all, I think you want there to be always one form in the formset in this case, and never more than one, so you’ll want to set max_num=1 and extra=1 in your ProfileInline.
Your core problem is that BaseFormSet._construct_form passes empty_permitted=True to each “extra” (i.e. empty) form in the formset. This parameter tells the form to bypass validation if its unchanged. You just need to find a way to set empty_permitted=False for the form.
You can use your own BaseInlineFormset subclass in your inline, so that might help. Noticing that _construct_form takes **kwargs and allows that to override the kwargs passed to the individual Form instances, you could override _construct_forms in your Formset subclass and have it pass empty_permitted=False in every call to _construct_form. The downside there is that you’re relying on internal APIs (and you’d have to rewrite _construct_forms).
Alternatively, you could try overriding the get_formset method on your ProfileInline, and after calling the parent’s get_formset, manually poke at the form inside the returned formset:
def get_formset(self, request, obj=None, **kwargs):
formset = super(ProfileInline, self).get_formset(request, obj, **kwargs)
formset.forms[0].empty_permitted = False
return formset
Play around and see what you can make work!
- [Django]-Django form – set label
- [Django]-Django DeleteView without confirmation template
- [Django]-How to completely uninstall a Django app?
8đź‘Ť
The easiest and most natural way to do that is via fomset clean()
:
class RequireOneFormSet(forms.models.BaseInlineFormSet):
def clean(self):
super().clean()
if not self.is_valid():
return
if not self.forms or not self.forms[0].cleaned_data:
raise ValidationError('At least one {} required'
.format(self.model._meta.verbose_name))
class ProfileInline(admin.StackedInline):
model = Profile
formset = RequireOneFormSet
(Inspired by this Matthew Flanagan’s snippet and Mitar’s comment below, tested to work in Django 1.11 and 2.0).
- [Django]-How do you include a csrf token when testing a POST endpoint in django?
- [Django]-How can I allow django admin to set a field to NULL?
- [Django]-Add a count field to a django rest framework serializer
8đź‘Ť
You need to set min_num in inline and validate_min in formset.
https://docs.djangoproject.com/en/1.8/topics/forms/formsets/#validate-min
class SomeInline(admin.TabularInline):
...
min_num = 1
def get_formset(self, request, obj=None, **kwargs):
formset = super().get_formset(request, obj=None, **kwargs)
formset.validate_min = True
return formset
- [Django]-AttributeError: 'module' object has no attribute 'tests'
- [Django]-How do I construct a Django reverse/url using query args?
- [Django]-Django download a file
3đź‘Ť
As of Django 3+
this is as simple as you like:
class EmployeeAddressMap(admin.StackedInline):
model = EmployeeAddress
min_num = 1
max_num = 1
can_delete = False #specified that this cannnot be removed
Happy coding
- [Django]-Silence tqdm's output while running tests or running the code via cron
- [Django]-Django-DB-Migrations: cannot ALTER TABLE because it has pending trigger events
- [Django]-Displaying graphs/charts in Django
1đź‘Ť
Mar, 2022 Update:
"min_num" can make inline fields required. (Inline fields are unrequired by default and 3 unrequired inline fields are displayed by default)
So, if you want 2 required inline fields, set "min_num = 2" as shown below and in this case below, 2 required and 3 unrequired inline fields are displayed and you can add more unrequired inline fields but you cannot add required inline fields in the form:
from django.contrib import admin
from .models import Profile, UserProfile
class ProfileInline(admin.TabularInline):
model = Profile
min_num = 2 # 2 required inline fields displayed
# 3 unrequired inline fields displayed by default
@admin.register(UserProfile)
class UserProfileAdmin(admin.ModelAdmin):
inlines = [ProfileInline]
In addition, you can use "extra" to set the number of unrequired fields displayed and you can use "max_num" to set the maximum number of the total inline fields including required and unrequired inline fields. So, in this case below, the maximum number of the total inline fields are 10 and 3 required and 2 unrequired inline fields are displayed and you can add 5 more unrequired inline fields as a maximum but you cannot add required inline fields in the form:
from django.contrib import admin
from .models import Profile, UserProfile
class ProfileInline(admin.TabularInline):
model = Profile
min_num = 3 # 3 required inline fields displayed
extra = 2 # 2 unrequired inline fields displayed
max_num = 10 # 10 inline fields as a maximum
@admin.register(UserProfile)
class UserProfileAdmin(admin.ModelAdmin):
inlines = [ProfileInline]
- [Django]-Mongoengine creation_time attribute in Document
- [Django]-Django self-recursive foreignkey filter query for all childs
- [Django]-Django render_to_string missing information