adjust forms that are generated by django generic editing views - python

I am trying to understand the process of generating generic form views in django. I have a generic view class with just
class BookUpdate(UpdateView):
model = Book
fields = [ 'name',
'pages',
'categorys'
]
which automatically generates a working html form from my model data. But now, I want to modify the field that is shown for categorys, is there any way to do this, or do I have to create a complete working BookForm class and custom BookUpdate class? Here its just 3 fields, but in my real case there are maybe 15 fields that I would need to code by myself, just because of a tiny change in the category field.
Cant I just overwrite the single field, using any class method?

You can either specify fields or form_class in your generic class-based view. With fields, Django will use a modelform_factory to generate the form. There's not much you can customise then.
You should create a BookForm class so that you can customise the fields. In your BookUpdate view, you only need to remove fields and add form_class = BookForm. Here I'm customising the widget for categorys and overriding the form field for pages:
def BookUpdate(UpdateView):
model = Book
form_class = BookForm
def BookForm(ModelForm):
pages = MyCustomPagesField()
class Meta:
model = Book
fields = '__all__'
widgets = {'categorys': MyCustomWidget()}
Note that you don't have to specify all fields, you can use "__all__" to have all fields or you can set exclude = [<list fields to exclude>] to just exclude a couple.

You don't have to code the fields yourself. But there is a small amount of work to do, as there isn't a method to override.
What you need to do is define a custom form. Since that will be a ModelForm, it will use the same logic to automatically create its fields based on the model. You can then override the definition of one of them.
class BookForm(forms.ModelForm):
categorys = forms.ModelMultipleChoiceField(custom_attributes_here...)
class Meta:
model = Book
fields = ["name", "pages", "categorys"]
And now tell your view to use that form:
class BookUpdate(UpdateView):
form_class = BookForm

Related

Show fields passing through a ManyToMany field django admin

I'm working on a project developed in Python 2.7 and Django 1.11.
I'm trying to show in admin page two fields passing through a ManyToMany field.
Here the models:
class ModelZero(Model):
# some fields
mtm_field = models.ManyToManyField(to="ModelOne", through="ModelTwo")
class ModelOne(Model):
# some fields
field_1_1 = models.CharField(unique=True, max_length=200)
field_1_2 = models.BooleanField(default=True)
class ModelTwo(Model):
# some fields
field_2_1 = models.ForeignKey('ModelOne', on_delete=models.CASCADE)
field_2_2 = models.BooleanField(default=True)
In the ModelZero admin page I want to show some fields from the ModelZero itself plus field_2_1 and field_2_2 from ModelTwo.
More in detail, the field_2_1 should be present using a custom widget.
Please note that ModelZeroAdmin is an inline ones.
Here the admin page:
class ModelZeroAdmin(DynamicRawIDMixin, admin.TabularInline):
model = ModelZero
fields = ('some', 'fields', 'field_2_2')
form = forms.ModelZeroForm
def field_2_2(self, obj):
return obj.mtm_field.through.field_2_2
Here the form:
class ModelZeroForm(ModelForm):
class Meta:
widgets = {
"mtm_field.through.field_2_1": dal.autocomplete.ModelSelect2Multiple(
url="my-autocomplete-url"
)
}
In this way i have two errors:
it's not possible add custom fields (field_2_2) in the fields tuple
custom widget is not showed
Is there a way to achieve this goal using this models structure?
I don't have experience with older Django version, but if I am not mistaken the syntax for a related field in admin interface would be something like :mtm_field__field_2_2.

Python/Django - Generating forms for models.

I have two questions concerning models and forms.
1) What is the best way to create automatically forms for the models?
In the example below I have two models - ModelA and ModelB. I need forms for them - ModelAForm and ModelBForm. They should be defined automatically. I do not want to do it manually, because in the future I will add other models, and all the forms will look the same. I am thinking about creating special decorator for models and use modelform_factory.
from django.db import models
from django.forms import ModelForm
class ModelA(models.Model):
...
class ModelB(models.Model):
...
class ModelAForm(ModelForm):
class Meta:
abstract = ModelA
class ModelBForm(ModelForm):
class Meta:
abstract = ModelB
2) Assuming I am using only ModelForm forms, it is possible to find the form for the model? Example. I have two models ModelA and ModelB, and two forms ModelAForm and ModelBForm. I have instance of ModelA and I would like to identify proper form for this model which I will pass to template - in this case ModelAForm.
Django provides some generic editing views. You only have to provide a model and the view will generate the form automatically. If you want to use the forms to create or update instances you can just use them.
If your really need to create the forms yourself you can use the modelform_factory that these views use themself to create the correct form for your model. But i would first go with the generic views as long as they can be modified to suit your needs.
Just pass it your model and optionally the fields you want. If you omit the fields, all fields will be generated:
from .models import MyModel
from django.forms.models import modelform_factory
my_form = modelform_factory(MyModel, fields=['name', 'age', 'job'])
There's ModelForm in django.
Here's fragment of documentation and link for it.
https://docs.djangoproject.com/en/1.10/topics/forms/modelforms/
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ['pub_date', 'headline', 'content', 'reporter']
You're probably looking for generic class-based views, where you only pass form_class and model instances, everything else django handles without your help.
Link for them - https://docs.djangoproject.com/en/1.10/ref/class-based-views/

Django multistep form wizard model form

Is there any way to use a Form Wizard in Admin interface for add/edit Models.
(using Django 1.5.2)
for example:
--models.py--
class AModel(models.Model):
fieldA = models.CharField(max_length=64)
fieldB = models.CharField(max_length=64)
--admin.py--
class Form1(ModelForm):
class Meta:
model = AModel
fields = ('fieldA',)
class Form2( ModelForm ):
class Meta:
model = AModel
fields = ('fieldB',)
.... something add for make this two forms in one multipage admin form , is that possible? or any other way to do the same job.
Thanks in advance.
It isn't possible by using admin module. The only way is either some external plugins or you need to create custom admin views. Google around..

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',]
...

Django ModelForm with User data in Generic View

I have a model with a foreign key to group (the other fields don't matter):
class Project(models.Model) :
group = models.ForeignKey(Group)
...
I have a model form for this model:
class AddProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = ["group","another"]
In my urls, I am using this in a generic view:
(r'^$', create_object, {'form_class':AddProjectForm, 'template_name':"form.html", 'login_required':True, 'extra_context':{'title':'Add a Project'}}),
That all works, but I want to have the group field display only the groups that the current user belongs to, not all of the groups available. I'd normally do this by passing in the user to the model form and overriding init if I wasn't in a generic view. Is there any way to do this with the generic view or do I need to go with a regular view to pass in that value?
This is gonna look dirty, since the generic view instantiates the form_class with no parameters. If you really want to use the generic_view you're gonna have to generate the class dynamically :S
def FormForUser(user):
class TmpClass(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(TmpClass, self).__init__(*args, **kwargs)
self.fields['group'].queryset = user.group_set.all()
class Meta:
model = Project
fields = ['group', 'another']
Then wrap the create object view
#login_required # Only logged users right?
def create_project(request):
user = request.user
form_class = FormForUser(user)
return create_object(request, form_class=form_class, ..... )
My recommendation is to write your own view, it will give you more control on the long term and it's a trivial view.
No, you'll need to make a regular view. As can be seen by looking at the source code for create_object(), there's no functionality to pass in extra parameters to the modelform (in django 1.2):
http://code.djangoproject.com/svn/django/branches/releases/1.2.X/django/views/generic/create_update.py

Categories