Django: Make certain fields in a ModelForm required=False - django

How do I make certain fields in a ModelForm required=False?
If I have:
class ThatForm(ModelForm):
class Meta:
widgets = {"text": Textarea(required=False)}
Or if I have:
class ThatForm(ModelForm):
text = Textarea(required=False)
Django returns:
__init__() got an unexpected keyword argument 'required'

following from comments. Probably yes:
class ThatForm(ModelForm):
def __init__(self, *args, **kwargs):
# first call parent's constructor
super(ThatForm, self).__init__(*args, **kwargs)
# there's a `fields` property now
self.fields['desired_field_name'].required = False

you ought to add blank=True to the corresponding model
The documentation says
If the model field has blank=True, then required is set to False on the form field. Otherwise, required=True.
Also see the documentation for blank itself.

When we need to set required option on a bunch of fields we can:
class ThatForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.Meta.required:
self.fields[field].required = True
class Meta:
model = User
fields = (
'email',
'first_name',
'last_name',
'address',
'postcode',
'city',
'state',
'country',
'company',
'tax_id',
'website',
'service_notifications',
)
required = (
'email',
'first_name',
'last_name',
'address',
'postcode',
'city',
'country',
)

the following may be suitable
class ThatForm(ModelForm):
text = forms.CharField(required=False, widget=forms.Textarea)

You could try this:
class ThatForm(ModelForm):
class Meta:
requireds =
{
'text':False,
}
requireds must be under Meta.

Related

Django: Add extra attributes to form fields generated by UpdateView

Am using a custom user that is a subclass of Django AbstractUser, what am trying to archive is to allow user update their data everything works but the form look ugly. Below is my code the class attribute is not added to the form.
forms.py(simplified)
class AccountEditForm(forms.ModelForm):
class Meta:
model = CustomUser
fields = ('first_name', 'last_name', 'phone_number', 'date_of_birth', 'country')
widget = {
'first_name':forms.TextInput(
attrs={
'class': 'input-bordered',
}
)
}
views.py
class UserAccountDetails(LoginRequiredMixin, UpdateView):
template_name = 'dashboard/account_edit.html'
context_object_name = 'form'
form_class = AccountEditForm
model = CustomUser
def get_object(self, queryset=None):
"""
Return the object the view is displaying.
"""
if queryset is None:
queryset = self.get_queryset()
#Get logged in user from request data
queryset = queryset.filter(pk=self.request.user.id)
try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
The widgets option is for overriding the defaults on explicitly declared fields. To add class to the field you have many options.
Option #1: Explicitly declare form field and add class through widgets in Meta.
class AccountEditForm(forms.ModelForm):
first_name = forms.TextField(widget=forms.TextInput())
class Meta:
model = CustomUser
fields = ('first_name', 'last_name', 'phone_number', 'date_of_birth', 'country')
widgets = {
'first_name': forms.TextInput(
attrs={
'class': 'input-bordered',
}
)
}
Option #2: Shorter version of option #1.
class AccountEditForm(forms.ModelForm):
first_name = forms.TextField(widget=forms.TextInput(attrs={'class': 'input-bordered'}))
class Meta:
model = CustomUser
...
Option #3: Add class in form's __init__ method.
class AccountEditForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(AccountEditForm, self).__init__(*args, **kwargs)
self.fields['first_name'].widget.attrs['class'] = 'input-bordered'
Option #4: Use django-widget-tweaks plugin.

tastypie check for missing fields in json

I am creating an api using tastypie. Code is as follows. I have inspected code using ipdb.
class UserProfileResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'userprofiles'
excludes = ['password', 'is_active', 'is_staff', 'is_superuser']
filtering = {
'username': ALL,
}
class UserResource(ModelResource):
user = fields.ForeignKey(UserProfileResource, 'user', full=True)
class Meta:
queryset = User.objects.all()
resource_name = 'users'
list_allowed_methods = ['get','post']
allowed_methods = ['get', 'put', 'patch', 'delete']
authentication = ApiKeyAuthentication()
serializer = Serializer(formats=['json', 'jsonp', 'xml', 'yaml', 'html', 'plist'])
def post_list(self, request, **kwargs):
print 'post list method'
mandatory_fields = ['username', 'email', 'first_name', 'last_name', 'address', 'city', 'pin']
fields_apicall = request.GET.keys()
#import ipdb;ipdb.set_trace();
print request.POST
if set(mandatory_fields).issubset(fields_apicall):
print request.GET.keys()
create_object = True
else:
print 'NO'
err_dict = { 'error':'Mandatory Fields Missing.' }
return HttpResponse(json.dumps(err_dict))
def obj_create(self, bundle, **kwargs):
print 'obj_create() '
mandatory_fields = ['username', 'email', 'first_name', 'last_name', 'address', 'city', 'pin']
fields_apicall = bundle.data.keys()
# import ipdb;ipdb.set_trace();
if set(mandatory_fields).issubset(fields_apicall):
bundle.obj = self._meta.object_class()
else:
err_dict = { 'error':'Mandatory Fields Missing.' }
return HttpResponse(json.dumps(err_dict))
I want to create nested user object, ie user object together with userprofile. For that I have to make sure all fields exists in the json. I tried overriding hydrate(),dehydrate(),and post_list() methods. Among these only post_list() get invoked on a post request.
I can check for fields in request json by overriding post_list() method but is this a good way to check for missing fields? I have googled and have gone through many SO posts but I didn't find any posts mentioning to override post_list() to check for missing fields some of them said to override hydrate(). Also is it necessary to override obj_create() method to create objects using tastypie?
obj_create() seems like the best place to do it. Something like this should work:
def obj_create(self, bundle, **kwargs):
mandatory_fields = ['username', 'email', 'first_name', 'last_name', 'address', 'city', 'pin']
for field_name, field_object in self.fields.items():
if field_name not in mandatory_fields:
err_dict = { 'error':'Mandatory Fields Missing.' }
return HttpResponse(json.dumps(err_dict))
super(SomeResource, self).obj_create(self, bundle, **kwargs)

conditionally change widget type in django form

I have the following simple form:
class ContactEmailForm(forms.ModelForm):
subject = forms.ChoiceField(choices=SUBJECT_TYPES)
class Meta:
model = ContactEmail
fields = ('name', 'email', 'subject', 'message',)
I want to conditionally change the subject field between a choice field and text input field.
How can I do this?
This could be accomplished by overriding the __init__ function within your ContactEmailForm class.
class ContactEmailForm(forms.ModelForm):
subject = forms.ChoiceField(choices=SUBJECT_TYPES)
def __init__(self, *args, **kwargs):
super(ContactEmailForm, self).__init__(*args, **kwargs)
if YOURCONDITION:
self.fields['subject'] = forms.CharField()
class Meta:
model = ContactEmail
fields = ('name', 'email', 'subject', 'message',)

How to position inlines in Django Admin in the list_display property?

I have two tables related Quiz and Difficulty_level:
I have created inline in admin.py like this:
class DifficultyLevelInline(admin.TabularInline):
model = DifficultyLevel
and included in QuizAdmin
To arrange the list order, I would do:
list_display = ('name', 'description', 'publication_date', 'category', 'is_active', 'is_premium')
How can I add inlines in the list_display order. I want to display The DifficultyLevelInline before category.
Unfortunately this is not possible using the default template.
If you take a look at change_form template:
https://github.com/django/django/blob/master/django/contrib/admin/templates/admin/change_form.html
You can see that inlines are always rendered after fieldsets.
One way to get around this would be to use other template:
class MyAdmin(admin.ModelAdmin):
list_display = ('name', 'description', 'publication_date', 'category', 'is_active', 'is_premium')
inlines = (DifficultyLevelInline,)
change_form_template = "my_change_form.html"
Grapelli supports it: https://django-grappelli.readthedocs.org/en/latest/customization.html#rearrange-inlines
Basically, it uses a placeholder via fieldsets and then moves them HTML via JavaScript:
https://github.com/sehmaschine/django-grappelli/blob/master/grappelli/templates/admin/change_form.html#L90-96 (search for placeholder, if the lines do not match anymore).
The same can be done by injecting custom javascript yourself (with or without using fieldsets as placeholders).
Suppose here is your inline model:
# models.py
from django.contrib.auth.models import Group
class MoreGroup(models.Model):
group = models.OneToOneField(Group, on_delete=models.CASCADE, related_name='more_group')
explain = models.CharField(verbose_name="explain_info", max_length=64, blank=True, null=True)
active = models.BooleanField(verbose_name="is_actived", default=True, blank=True, null=True)
then do this:
# admin.py
from . import models
class MoreGroupInline(admin.StackedInline):
model = models.MoreGroup
can_delete = False
verbose_name_plural = 'more_info'
class MyGroupAdmin(GroupAdmin):
list_display = ['id', 'name', 'get_inline_info']
def get_inline_info(self, obj) -> str:
mg = models.MoreGroup.objects.filter(group=obj)
if mg.count():
return mg[0].explain
else:
return '-'
get_inline_info.short_description = 'explain_info'
admin.site.register(models.Group, MyGroupAdmin)
I found a solution here: https://blog.devgenius.io/django-admin-dynamic-inline-positioning-7208596479ce
class MyAdmin(admin.ModelAdmin):
list_display = ('name', 'description', 'difficulty_level_inline', 'publication_date', 'category', 'is_active', 'is_premium')
inlines = (DifficultyLevelInline,)
def difficulty_level_inline(self, *args, **kwargs):
context = getattr(self.response, 'context_data', None) or {}
inline = context['inline_admin_formset'] = context['inline_admin_formsets'].pop(0)
return get_template(inline.opts.template).render(context, self.request)
def render_change_form(self, request, *args, **kwargs):
self.request = request
self.response = super().render_change_form(request, *args, **kwargs)
return self.response

BooleanField default value not set when creating a model instance

When creating an instance of a model having a BooleanField my_boolean_field with a default set to True, I get an error:
my_boolean_field is required
Shouldn't it be set to the default value?
models.py
class MyModel(User):
my_boolean_field = models.BooleanField(default=False)
admin.py
class MyModelCreationForm(UserCreationForm):
my_boolean_field = forms.BooleanField(initial=False)
class Meta:
model = User
class MyModelChangeForm(UserChangeForm):
my_boolean_field = forms.BooleanField(initial=False)
class Meta:
model = User
class MyModelAdmin(UserAdmin):
form = MyModelChangeForm
add_form = MyModelCreationForm
list_filter = ()
list_display = ('username', 'my_boolean_field')
fieldsets = (
(None, {'fields': ('username', 'my_boolean_field', 'password' )}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('username', 'my_boolean_field', 'password1', 'password2')}
),
)
def get_form(self, request, obj=None, **kwargs):
form = super(MyModelAdmin, self).get_form(request, obj, **kwargs)
if obj==None:
form.base_fields['username'].widget.attrs['autocomplete'] = 'off'
form.base_fields['password1'].widget.attrs['autocomplete'] = 'off'
form.base_fields['password2'].widget.attrs['autocomplete'] = 'off'
return form
samsic_site.register(MyModel, MyModelAdmin)
Change field definition in your model form to specify require=False.
class MyModelCreationForm(UserCreationForm):
my_boolean_field = forms.BooleanField(initial=False, required=False)
class Meta:
model = User
Note on BooleanField reference
Note
Since all Field subclasses have required=True by default, the validation condition here is important. If you want to include a boolean in your form that can be either True or False (e.g. a checked or unchecked checkbox), you must remember to pass in required=False when creating the BooleanField.