I have a multiple ModelForm classes that each represent a different Model. I would like to have a generic 'create' function that loads the specified model form based on a URL parameter. It is possible to load a model dynamically with this:
model_name = 'TestModel'
m = get_model('AppLabel', model_name)
Does anyone know how I can achieve the same for ModelForms, something like:
modelform_name = 'TestModelForm'
f = get_form('AppLabel', modelform_name)
if f.is_valid():
...
I can not think of a way to do this with generic views - they require the ModelForm to be passed, rather than just its name. If I get the model with get_model then pass that to the generic view it will display a form but I am unable to exclude model fields.
TIA for any tips
When you create a ModelForm it does not register itself with its model's app. (Based on experience and a quick browse through the source).
Here are some otheroptions I can think of:
All ModelForm classes exist in a single module: Use getattr on that module based on the string.
ModelForm's are spread out among many models and you have a reasonable (<30) amount of forms:
Create a dictionary mapping from form strings you expect to ModelForm classes. For example:
from some_app.forms import FirstModelForm
from another_app.forms import SecondModelForm
from additional_app.forms import FirstModelForm as AdditionalAppFirstModelForm # Will allow for managing conflicting names easily.
form_mapping = {
'FirstModelForm': FirstModelForm,
'SecondModelForm': SecondForm,
'AdditionalAppFirstModelForm': AdditionalAppFirstModelForm,
}
request_form_class = request.POST.get('form_class')
f = form_mapping.get(request_form_class)(request.POST)
if f.is_valid():
f.save()
You're dealing with a lot of forms: Create a baseclass for your ModelForm, or replace the BaseModelFormMetaclass at runtime. You'll have to deal with issues such as name conflicts, duplicate ModelForms for the same Model "automagically", so prepare for some headaches. It would be pretty rad if you could pull it off.
Personally (as you can probably see), I'd just go with option #2.
An alternate method for this is to replace forms.py with a package called forms. Then, in __init__.py within that package, import all your ModelForms.
Then you can use sdolan's Option #1.
Related
I have a model called Course:
class Course(models.Model):
number_of_semesters = models.PositiveIntegerField()
field = models.CharField(max_length=30)
qualification = models.ForeignKey(Qualification, on_delete=models.CASCADE)
I am trying to get a form in which you can input as many courses as the user wants from the webpage. How will I do this?
I know this is an old one, but I would recommend you to use Django Rest Framework. Although it is kind of tricky at first, you can use the ViewSets and Serializers to get multiple objects and save them in your database at once. (BTW, even though it is used for API's you can easily substitute the normal Django views with the ViewSets and use them as a standard).
I know this is not actually what you asked, but I have been developing in Django for a while now and I haven't been able to use the formsets in a clean way to save N objects without knowing N at first.
If you decide to go with my proposal, I would recommend you to read the following:
Viewsets
Serializers (they are basically the same as the DjangoForms)
Nested Serializers (for rendering/creating/linking your ForeignKey instance)
# SERIALIZER
from rest_framework import serializers
class QualificationSerializer(serializers.ModelSerializer):
class Meta:
model = Qualification
fields = (
# Insert the fields here, just like a form
)
class CourseSerializer(serializers.ModelSerializer):
qualification = QualificationSerializer() # Nested serializer
class Meta:
model = Course
fields = (
'number_of_semesters', 'field', 'qualification',
)
One way you could do this is to not use formsets but to get creative with the prefix that you use to load a form with. For example the + button loads an empty form with a prefix based on a counter posted to the view (including a tag, something like "course-4", so you get the form with SomeForm(request.POST, prefix="course-4")). When it's time to validate/save the view you simply parse the prefixes (with a regex) from the POST and save a form for every one of them.
It seems like a bit more work than to simply use formsets but every time I've tried to use them I had to abandon them at some point because they didn't provide enough flexibility.
I am trying to create an edit form for my model. I did not use a model form because depending on the model type, there are different forms that the user can use. (For example, one of the forms has a Tinymce widget, while the other doesn't.)
Is there any way of setting the initial data of a form (not a ModelForm) using a model?
I tried the following but getting an error:
b = get_object_or_404(Business, user=request.user)
form = f(initial = b)
where f is a subclass of forms.Form
The error I am getting is AttributeError: 'Business' object has no attribute 'get'
The initial data needs to be a dict (or at least have a dict-like interface, which a Django model does not have).
You can construct a dict from your model using django.forms.models.model_to_dict:
from django.forms.models import model_to_dict
b_as_dict = model_to_dict(b)
This is the same function Django's built in ModelForm class uses to set its initial data, and while you've specified that you don't want to use a ModelForm it may be more convenient to find some way of structuring your code that allows you to do so.
There is a more direct way of using model data than dict. See sample in documentation.
b = get_object_or_404(Business, user=request.user)
form = f(instance = b)
I have dozens of Models, each with ONE associated ModelForm (whose Meta.model refers to the Model in question).
E.g.
class FooModel(Model):
pass
class FooModelForm(ModelForm):
class Meta:
model = FooModel
# current approach using a classmethod
FooModelForm.insert_in_model() # does cls.Meta.model.form = cls
So, obviously, it's easy to find FooModel given FooModelForm. What I want is to know the best way to do the REVERSE: find FooModelForm when I am presented with FooModel or even the string "Foo".
Assume only one ModelForm for each model, although solutions that return multiple are fine.
My current approach is to stash the model in the form class (as shown above), but I'm interested in knowing better approaches especially ones that could compute it centrally (without the final line above).
EDIT: I've reviewed things like Django: Display Generic ModelForm or predefined form but I believe this is a simpler question than those. The Django admin code must do something along the lines of what I seek. But get_model equivalent for ModelForms? suggests that might be voodoo and that it would be best to just do dict['Foo']=FooModelForm or its equivalent to keep track of the association explicitly. Seems repetitious.
If you have under 20 forms, sounds like mapping out a dictionary is the easiest way. Django does this kinda thing internally too.
For ModelForms, django admin just creates them on the fly via modelform_factory, so there is no comparable method to get_model
I do see, your method is bullet proof, but requires a line in ever model def.
If you only have one ModelForm per model, you could potentially iterate through the ModelForm subclasses until you find your form.
find FooModelForm when I am presented
with FooModel or even the string
"Foo".
modelforms = forms.ModelForm.__subclasses__()
def get_modelform(model):
try:
return filter(lambda x:x.Meta.model == model, modelforms)[0]
except IndexError:
print "apparently, there wasn't a ModelForm for your model"
If you want to pull the ModelForm as a string, you'll need to make sure both
app_label and __name__ are correct, which means it will be easier to use get_model('app', 'model') in the function.
You could combine this with your method and automatically place an attribute on your models that point to its ModelForm.
Hook into the class_prepared signal at the top of your apps, find the corresponding ModelForm and attach it to your Model class.
Hope that helps or gives you some ideas.
I have the following model and it's form:
class Project(models.Model)
class ProjectForm(forms.ModelForm)
class Meta:
Model = Project
So it's easy to create a form by instantiating:
form = ProjectForm()
But in my case, I have several models aside from "Projects", and I don't know in advance for which of these models I will need to create the form.
So I would like to create the form from the ContentType instance of the Project model.
In other words, I'm looking for something that looks like:
myproject = Project()
form = createform(myproject.ContentType)
Presumably you have a certain limited selection of models that might be used. The simplest way is just to create form classes for each of them, then choose the one you need from a dictionary:
MODEL_FORMS = {
MyModel: MyModelForm,
MyOtherModel: MyOtherModelForm
}
my_form_class = MODEL_FORMS[my_project.content_type]
my_form = my_form_class()
Unfortunately, this was the best I could find - but a combination of get_model and form_for_model should do the trick. You'll need to use get_model to load up the model type you want to work on, and then form_for_model to get a form for that model.
Edit: Daniel's solution is a much better one if you know what models you're dealing with.
Thank you to both of you, this helps a lot !
I will go with Daniel's solution as I have a limited number of models.
I think maybe I will need to add model_class() to "my_project.content_type.model_class()" in order to get the model class (to be checked) ?
Just for the record, I had managed to make something work with model formset factories :
from django.forms.models import modelformset_factory
ProjectFormSet = modelformset_factory(my_project.content_type.model_class())
my_form = ProjectFormSet()
but this form would of course not get all the customisations made in my model forms... so that was not a good solution.
I have a model formset on a model with a couple of date fields - which again is a DateTimeField in the model.
But when it displays in the template, it is shown as a text field.
How can I show it as a drop down box ?
WFormSet = inlineformset_factory(UserProfile, W, can_delete=False,exclude=[<list of fields to be excluded>], extra=3)
This is how I am intializing the modelformset.
How do I override these settings
The usual way to do this is to override the default field types in your ModelForm definition.
The example below would work if you had a DateField in your model (I note you have a DateTimeField... I'll come back to that in a sec). You're going to be specifying the exact same field type as would normally be specified, but you'll pass a different widget to the form field constructor.
from django.db import models
from django import forms
from django.forms.extras import SelectDateWidget
class MyModel(models.Model):
a_date_field = models.DateField()
class MyModelForm(forms.ModelForm):
a_date_field = forms.DateField(widget=SelectDateWidget())
class Meta:
model = MyModel
There isn't, to my knowledge, an equivalent widget provided for the DateTimeField in Django 1.0.x. In this case, you'll want to make a custom widget, perhaps subclassing SelectDateWidget. I note from a quick google on SelectDateTimeWidget that there have been several others who've been making what appear to be the widget you're seeking. I've not used them, so caveat emptor, but something like this SelectDateTimeWidget patch might be a good place to start.
Edit: When using a ModelFormset or InlineModelFormset, you can still achieve this by passing form=MyModelForm to the inlineformet_factory function:
MyModelFormset = inlineformset_factory(MyParentModel, MyModel, form=MyModelForm)
This works because the two model formset factories actually call the regular formset_factory constructor in their own implementations. It's a little hard to figure out because it's not explicitly stated in the docs... rather the Django docs allude to this ability by mentioning in passing that the model formset factories extend the normal formset_factory. Whenever I'm in doubt, I open django/forms/models.py to check out the full signature of the formset factory functions.