Django: form save exculde certain field - python

I do not understand the official document about exclude .
Set the exclude attribute of the ModelForm‘s inner Meta class to a list of fields to be excluded from the form.
For example:
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
exclude = ['title']
Since the Author model has the 3 fields name, title and birth_date, this will result in the fields name and birth_date being present on the form.
My understanding is as follows: django form save method will save all form data.If one set exclude =('something',) , 'something' field will not show on frontend and wouldn't be save while calling form save method.
But when I do as the document saying, 'something' field still show.What's the matter?
I also want to add some fields to a form for validating which can show on frontend without saving.It is stange that I find nothing about this need.
**update**
my code :
class ProfileForm(Html5Mixin, forms.ModelForm):
password1 = forms.CharField(label=_("Password"),
widget=forms.PasswordInput(render_value=False))
password2 = forms.CharField(label=_("Password (again)"),
widget=forms.PasswordInput(render_value=False))
captcha_text = forms.CharField(label=_("captcha"),
widget=forms.TextInput())
captcha_detext = forms.CharField(
widget=forms.HiddenInput())
class Meta:
model = User
fields = ("email", "username")
exclude = ['captcha_text']
def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
..........
def clean_username(self):
.....
def clean_password2(self):
....
def save(self, *args, **kwargs):
"""
Create the new user. If no username is supplied (may be hidden
via ``ACCOUNTS_PROFILE_FORM_EXCLUDE_FIELDS`` or
``ACCOUNTS_NO_USERNAME``), we generate a unique username, so
that if profile pages are enabled, we still have something to
use as the profile's slug.
"""
..............
def get_profile_fields_form(self):
return ProfileFieldsForm
if exclude only affect the model defined under class Meta , so exclude = ['captcha_text'] would not work?

exclude = ['title'] will exclude the field from the form, not from the model.
form.save() will try to save the model instance with the available for fields, but model might throw any error pertaining to the missing field.
To add extra fields in model form, do this:
class PartialAuthorForm (ModelForm):
extra_field = forms.IntegerField()
class Meta:
model = Author
def save(self, *args, **kwargs):
# do something with self.cleaned_data['extra_field']
super(PartialAuthorForm, self).save(*args, **kwargs)
But make sure there is no field called "PartialAuthorForm" in the model Author.

First, the reason why your title field is still displayed must be somewhere in your view. Be sure that you create your (unbound) form instance like this:
form = PartialAuthorForm()
and try this simple rendering method in the template
{{ form.as_p }}
Second, it should be no problem to add extra fields to a model form, see e.g. this post.

Related

Automatically updating a model field when it is created

I would like to automatically update a model field when it is created. So this is my situation. I have a custom User model that my customer can use to login. After they login, they will go to the account/profile page, which has a link to a form. Currently, when the user submits the form, it creates an instance of the LevelTest model(which is something I just need for the website to work). Here is the view class for the form:
class LevelTestView(generic.CreateView):
template_name = "leads/leveltest.html"
form_class = LevelTestModelForm
def get_success_url(self):
return reverse("profile-page")
and here is the LevelTestModelForm:
class LevelTestModelForm(forms.ModelForm):
class Meta:
model = LevelTest
fields = (
'first_name',
'last_name',
'age',
'username',
)
What I want to fill in automatically is the username field. In fact, I wish it doesn't even show up on the form itself when the user types in. The username is a field in the User Model, so I just want the new LevelTest's username field filled in with the current user's username. Hence, I used a post_save signal like below(which doesn't work):
def post_leveltest_created_signal(sender, instance, created, **kwargs):
if created:
instance.objects.update(
username=instance.user.username,
description='Add Description',
phone_number=instance.user.cellphone,
email=instance.user.username,
)
post_save.connect(post_leveltest_created_signal, sender=LevelTest)
I hope you guys could help me tweek the post_save signal, so that when the user creates a LevelTest instance, the LevelTest's username field(as well as the phone_number and email) is filled in with the user model's information. Thanks a lot!
If I understand you correct, you don't need to use signals, you can save username easier:
Extend get_form_kwargs method in your CreateView, like that:
class LevelTestView:(generic.CreateView)
...
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
Extend __init__ and save method in your Form, like that:
class LevelTestModelForm(forms.ModelForm):
...
def __init__(self, user, *args, **kwargs):
self.user = user
super().__init__(*args, **kwargs)
...
def save(self, commit=True):
leveltest = super().save(commit=False)
# I think it would be better if you saved only 'user' instance
# like this - leveltest.user = self.user (of course if you have fk to user model)
leveltest.username = self.user.username
leveltest.phone_number=self.user.cellphone
leveltest.email=self.user.username
leveltest.save()
return leveltest
I think #KIN1991's answer is pretty awesome, but you can minimize/optimize the code even more by just overriding the form_valid method. Like this:
class LevelTestView:(generic.CreateView)
...
def form_valid(self, form, *args, **kwargs):
user = self.request.user
form.instance.username = user.username
form.instance.phone_number=user.cellphone,
form.instance.email=user.username
return super().form_valid(form, *args, **kwargs)

Django UpdateView not saving whether new user or existing user using ModelForm

I am writing an UpdateView UpdateAccountView for the User model, updating through a ModelForm MyUserCreationForm which is already the ModelForm used for creating new users. The problem is that whenever I click Submit to save the changes in the template, it rerenders the template.
For instance, if I didn't change any fields, it gives me error of "Username is already taken" which I will show you in the MyUserCreationForm to check for unique usernames, or just rerenders the template for new entries on the fields, without actually saving any changes to the model.
Here is my MyUserCreationForm
class MyUserCreationForm(UserCreationForm):
class Meta:
model = User #extended from auth.models.User
fields = ("first_name", "last_name", "username", "email", "gender", "profile_photo")
# adding bootstrap styling to the ModelForm fields
def __init__(self, *args, **kwargs):
super(MyUserCreationForm, self).__init__(*args, **kwargs)
for field in iter(self.fields):
self.fields[field].widget.attrs.update({
'class': 'form-control input-lg',
'placeholder': field.replace("_", " ").title(),
'tabindex': list(self.fields).index(field) + 1})
self.fields[field].widget.attrs.pop('autofocus', None)
if field == 'username' or field == 'email':
self.fields[field].widget.attrs.update({
'placeholder': field.replace("_", " ").title() + ' *',
})
def clean_username(self):
username = self.cleaned_data['username']
if not re.search(r'^[\w.-]+$', username):
raise forms.ValidationError('Username can only contain alphanumeric characters, dots, hyphens ,and underscores')
try:
User.objects.get(username=username)
except ObjectDoesNotExist:
return username
raise forms.ValidationError('Username is already taken.')
and here is the view class UpdateAccountView
class UpdateAccountView(UpdateView):
form_class = MyUserCreationForm
model = User
template_name = 'auth/account-edit.html'
success_url = '/'
def get_object(self, queryset=None):
return self.request.user
However, if I directly update the Model by using model and fields in the UpdateView, it works fine. But I need to do it through the ModelForm to have control over the styles when rendering.
So I know that the problem lies within ModelForm but I cannot find it, even after searching a lot.
Thank you in advance.
You could try moving the code that styles the fields in to a separate mixin:
class UserStyleMixin(object):
def __init__(self, *args, **kwargs):
super(UserStyleMixin, self).__init__(*args, **kwargs)
# Style the fields here
Then you can make your MyUserCreationForm use the mixin, and create a new form for the update view.
class MyUserCreationForm(UserStyleMixin, UserCreationForm):
...
class UserUpdateForm(UserStyleMixin, forms.ModelForm):
...
Note that if the update view allows the user to change the username, then you should still check that the username is allowed and unique. If you have unique=True for the username in your model, Django should take care of this for you. It would be a good idea to move the username regex to the model as well.

Django: How to prevent model form fields from rendering but include them in validation?

Let's say I have the following model:
class Folder(models.Model):
name = models.CharField(default='untitled', max_length=255)
parent = models.ForeignKey('self', null=True, blank=True)
root = models.ForeignKey('self', null=True, blank=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
In my app, this class is used to represents two types of folders: a normal folder-object and a so called root_folder-object, which does not have a parent nor a root-FK set.
I created a custom ModelForm with custom clean(), which is working like a charm (according to unittests and manual testing):
class FolderForm(ModelForm):
def __init__(self, *args, **kwargs):
try:
data = kwargs.get('data', None).copy()
except AttributeError:
data = None
self.prefix = kwargs.get('prefix')
user = kwargs.pop('user', None)
if data is not None:
if user is not None:
data[self.add_prefix('user')] = user.id
kwargs['data'] = data
super(FolderForm, self).__init__(*args, **kwargs)
def clean(self):
# just working fine, so I won't include it here
pass
class Meta:
model = Folder
fields = '__all__'
So, because my root-folder is just a normal Folder-object with blank FKs, I don't want to even show these fields to the user while creation. I created another form for this:
class AddRootFolderForm(FolderForm):
class Meta:
model = Folder
exclude = ['parent', 'root', 'user']
As you can see, I exclude user aswell, this value will be set in the view. Currently, this is my view code:
#login_required
def create_rootfolder(request):
if request.method == 'POST':
form = FolderForm(data = request.POST,
user = request.user)
else:
form = AddRootFolderForm()
if form.is_valid():
new = form.save()
return redirect('show_rootfolder', root_id = new.id)
return render(request, 'create_rootfolder.html',
{ 'form': form })
This whole setup is working, but seems awful hackerish. Is there any better approach to hide certain fields from the user (meaning: Don't even show them as hidden fields), but include them in validation? My main problem is, that I can't use the same form for displaying and validating, because the excluded fields will not be validated, if I use AddRootFolderForm as single form instance.
I am aware that I can exclude the fields dynamically in the constructor, I even tried this, but it got my constructor bloated to 50 LOC, which seemed unclean.
So what would be the best approach to validate the model with all fields, even if they were not included in the form shown to the user?
Why validate fields, not used in Form?
The cleaning process is the way to check the data posted by a user. The rest of the data, required for Model operations must be added after the form validation
if form.is_valid():
new = form.save(commit=False)
new.user = request.user
...
new.save()

Add a field to the ModelAdmin Form

I want to give users the possibility to create multiple events at once. Therefore I would like to add a field to the admin-add-page where a number of repetitions can be specified. Then I want to override the save function and create multiple events (based on the input). I started writing some code but the admin add page does not update at all. I will show you the code below:
In admins.py:
class EventAdmin(admin.ModelAdmin):
form = EventForm
admin.site.register(Event, EventAdmin)
In forms.py
from django import forms
from django.db import models
from calendar_app.models import Event
class EventForm(forms.ModelForm):
name = models.CharField(max_length=100) # just for testing purpose
class Meta:
model = Event
def __init__(self, *args, **kwargs):
super(EventForm, self).__init__(*args, **kwargs)
if not kwargs.has_key('instance'):
self.fields['name'] = forms.CharField(label='Name')
self.base_fields['name'] = forms.CharField(label='Name')
def save(self, commit=True):
model = super(EventForm, self).save(commit=False)
# Save all the fields...
if commit:
model.save()
return model
But the "name" field is not showing up when I add an event. Any ideas? Thanks!
I used models.CharField instead of forms.CharField. See comments.

Add custom form fields that are not part of the model (Django)

I have a model registered on the admin site. One of its fields is a long string expression. I'd like to add custom form fields to the add/update pages of this model in the admin. Based on the values of these fields I will build the long string expression and save it in the relevant model field.
How can I do this?
I'm building a mathematical or string expression from symbols. The user chooses symbols (these are the custom fields that are not part of the model) and when they click save then I create a string expression representation from the list of symbols and store it in the DB. I don't want the symbols to be part of the model and DB, only the final expression.
Either in your admin.py or in a separate forms.py you can add a ModelForm class and then declare your extra fields inside that as you normally would. I've also given an example of how you might use these values in form.save():
from django import forms
from yourapp.models import YourModel
class YourModelForm(forms.ModelForm):
extra_field = forms.CharField()
def save(self, commit=True):
extra_field = self.cleaned_data.get('extra_field', None)
# ...do something with extra_field here...
return super(YourModelForm, self).save(commit=commit)
class Meta:
model = YourModel
To have the extra fields appearing in the admin just:
Edit your admin.py and set the form property to refer to the form you created above.
Include your new fields in your fields or fieldsets declaration.
Like this:
class YourModelAdmin(admin.ModelAdmin):
form = YourModelForm
fieldsets = (
(None, {
'fields': ('name', 'description', 'extra_field',),
}),
)
UPDATE:
In Django 1.8 you need to add fields = '__all__' to the metaclass of YourModelForm.
It it possible to do in the admin, but there is not a very straightforward way to it. Also, I would like to advice to keep most business logic in your models, so you won't be dependent on the Django Admin.
Maybe it would be easier (and maybe even better) if you have the two seperate fields on your model. Then add a method on your model that combines them.
For example:
class MyModel(models.model):
field1 = models.CharField(max_length=10)
field2 = models.CharField(max_length=10)
def combined_fields(self):
return '{} {}'.format(self.field1, self.field2)
Then in the admin you can add the combined_fields() as a readonly field:
class MyModelAdmin(models.ModelAdmin):
list_display = ('field1', 'field2', 'combined_fields')
readonly_fields = ('combined_fields',)
def combined_fields(self, obj):
return obj.combined_fields()
If you want to store the combined_fields in the database you could also save it when you save the model:
def save(self, *args, **kwargs):
self.field3 = self.combined_fields()
super(MyModel, self).save(*args, **kwargs)
Django 2.1.1
The primary answer got me halfway to answering my question. It did not help me save the result to a field in my actual model. In my case I wanted a textfield that a user could enter data into, then when a save occurred the data would be processed and the result put into a field in the model and saved. While the original answer showed how to get the value from the extra field, it did not show how to save it back to the model at least in Django 2.1.1
This takes the value from an unbound custom field, processes, and saves it into my real description field:
class WidgetForm(forms.ModelForm):
extra_field = forms.CharField(required=False)
def processData(self, input):
# example of error handling
if False:
raise forms.ValidationError('Processing failed!')
return input + " has been processed"
def save(self, commit=True):
extra_field = self.cleaned_data.get('extra_field', None)
# self.description = "my result" note that this does not work
# Get the form instance so I can write to its fields
instance = super(WidgetForm, self).save(commit=commit)
# this writes the processed data to the description field
instance.description = self.processData(extra_field)
if commit:
instance.save()
return instance
class Meta:
model = Widget
fields = "__all__"
You can always create new admin template, and do what you need in your admin_view (override the admin add URL to your admin_view):
url(r'^admin/mymodel/mymodel/add/$','admin_views.add_my_special_model')
If you absolutely only want to store the combined field on the model and not the two seperate fields, you could do something like this:
Create a custom form using the form attribute on your ModelAdmin. ModelAdmin.form
Parse the custom fields in the save_formset method on your ModelAdmin. ModelAdmin.save_model(request, obj, form, change)
I never done something like this so I'm not completely sure how it will work out.
The first (highest score) solution (https://stackoverflow.com/a/23337009/10843740) was accurate, but I have more.
If you declare fields by code, that solution works perfectly, but what if you want to build those dynamically?
In this case, creating fields in the __init__ function for the ModelForm won't work. You will need to pass a custom metaclass and override the declared_fields in the __new__ function!
Here is a sample:
class YourCustomMetaClass(forms.models.ModelFormMetaclass):
"""
For dynamically creating fields in ModelForm to be shown on the admin panel,
you must override the `declared_fields` property of the metaclass.
"""
def __new__(mcs, name, bases, attrs):
new_class = super(NamedTimingMetaClass, mcs).__new__(
mcs, name, bases, attrs)
# Adding fields dynamically.
new_class.declared_fields.update(...)
return new_class
# don't forget to pass the metaclass
class YourModelForm(forms.ModelForm, metaclass=YourCustomMetaClass):
"""
`metaclass=YourCustomMetaClass` is where the magic happens!
"""
# delcare static fields here
class Meta:
model = YourModel
fields = '__all__'
This is what I did to add the custom form field "extra_field" which is not the part of the model "MyModel" as shown below:
# "admin.py"
from django.contrib import admin
from django import forms
from .models import MyModel
class MyModelForm(forms.ModelForm):
extra_field = forms.CharField()
def save(self, commit=True):
extra_field = self.cleaned_data.get('extra_field', None)
# Do something with extra_field here
return super().save(commit=commit)
#admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
form = MyModelForm
You might get help from my answer at :
my response previous on multicheckchoice custom field
You can also extend multiple forms having different custom fields and then assigning them to your inlines class like stackedinline or tabularinline:
form =
This way you can avoid formset complication where you need to add multiple custom fields from multiple models.
so your modeladmin looks like:
inlines = [form1inline, form2inline,...]
In my previous response to the link here, you will find init and save methods.
init will load when you view the page and save will send it to database.
in these two methods you can do your logic to add strings and then save thereafter view it back in Django admin change_form or change_list depending where you want.
list_display will show your fields on change_list.
Let me know if it helps ...
....
class CohortDetailInline3(admin.StackedInline):
model = CohortDetails
form = DisabilityTypesForm
...
class CohortDetailInline2(admin.StackedInline):
model = CohortDetails
form = StudentRPLForm
...
...
#admin.register(Cohort)
class CohortAdmin(admin.ModelAdmin):
form = CityInlineForm
inlines = [uploadInline, cohortDetailInline1,
CohortDetailInline2, CohortDetailInline3]
list_select_related = True
list_display = ['rto_student_code', 'first_name', 'family_name',]
...

Categories