I'm making a settings interface which works by scanning for a settings folder in the installed applications, scanning for settings files, and finally scanning for ModelForms.
I'm at the last step now. The forms are properly found and loaded, but I now need to provide the initial data. The initial data is to be pulled from the database, and, as you can imagine, it must be limited to the authenticated user (via request.user.id).
Keep in mind, this is all done dynamically. None of the names for anything, nor their structure is known in advanced (I really don't want to maintain a boring settings interface).
Here is an example settings form. I just pick the model and which fields the user can edit (this is the extent to which I want to maintain a settings interface).
class Set_Personal_Info(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('nick_name', 'url')
I've looked at modelformset_factory which almost does what I want to do, but it only seems to work with results of two or more. (Here, obj is one of the settings forms)
Formset = modelformset_factory(obj.Meta.model, form=obj)
Formset(queryset=obj.Meta.model.objects.filter(id=request.user.id))
I can't filter the data, I have to get one, and only one result. Unfortunately I can't use get()
Formset = modelformset_factory(obj.Meta.model, form=obj)
Formset(queryset=obj.Meta.model.objects.get(id=request.user.id))
'User' object has no attribute 'ordered'
Providing the query result as initial data also doesn't work as it's not a list.
Formset = modelformset_factory(obj.Meta.model, form=obj)
Formset(initial=obj.Meta.model.objects.get(id=request.user.id))
'User' object does not support indexing
I have a feeling that the answer is right in front of me. How can I pull database from the database and shove it into the form as initial values?
I'm not really sure I understand what you're trying to do - if you're just interested in a single form, I don't know why you're getting involved in formsets at all.
To populate a modelform with initial data from the database, you just pass the instance argument:
my_form = Set_Personal_Info(instance=UserProfile.objects.get(id=request.user.id))
Don't forget to also pass the instance argument when you're instantiating the form on POST, so that Django updates the existing instance rather than creating a new one.
(Note you might want to think about giving better names to your objects. obj usually describes a model instance, rather than a form, for which form would be a better name. And form classes should follow PEP8, and probably include the word 'form' - so PersonalInfoForm would be a good name.)
Based on what I've understand ... if you want to generate a form with dynamic fields you can use this:
class MyModelForm(forms.ModelForm):
def __init__(self, dynamic_fields, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.fields = fields_for_model(self._meta.model, dynamic_fields, self._meta.exclude, self._meta.widgets)
class Meta:
model = MyModel
Where dynamic_fields is a tuple.
More on dynamic forms:
http://www.rossp.org/blog/2008/dec/15/modelforms/
http://jacobian.org/writing/dynamic-form-generation/
http://dougalmatthews.com/articles/2009/dec/16/nicer-dynamic-forms-django/
Also Daniel's approach is valid and clean ... Based on your different ids/types etc you can you use different Form objects
forms.py
class MyModelFormA(forms.ModelForm):
class Meta:
model = MyModel
fields = ('field_a','field_b','field_c')
class MyModelFormB(forms.ModelForm):
class Meta:
model = MyModel
fields = ('field_d','field_e','field_f')
views.py
if request.method == 'POST':
if id == 1:
form = MyModelFormA(data=request.POST)
elif id == 2:
form = MyModelFormB(data=request.POST)
else:
form = MyModelFormN(data=request.POST)
if form.is_valid():
form.save() else:
if id == 1:
form = MyModelFormA()
elif id == 2:
form = MyModelFormB()
else:
form = MyModelFormN()
Related
I have a form base class which checks if the instance the form is updating has changed, and does not save if it has not changed.
this is in my custom model form, I override save:
class MyModelForm(models.ModelForm):
# .. more code here..
def save(self, commit=True):
if self.has_changed():
# Won't do anything if the instance did not changed
return self.instance
return super(MyModelForm, self).save(commit)
A LOT of my forms use this base class.
Now, one of my forms have a few fields which I set to disabled=True (django 1.9 +). So in one of my forms:
def __init__(self, *args, **kwargs):
## ..code
self.fields['address'].disabled = True
After a lot of debugging why the form.has_changed() is True (hence the instance is saved for no reason), even when I save the form without changing the instance. I've found out that django includes disabled fields in changed_data - which makes no sense, as disabled fields should not be altered by the user anyway.
Am I missing something or it is a bug, or maybe that how it should work?
How can I resolve this without too much changes, as the form base class is used a lot in my code.
This is a known issue with DjangoProject with the ticket at https://code.djangoproject.com/ticket/27431 and the corresponding PR at https://github.com/django/django/pull/7502. As this answer is being written the PR is merged with master so the latest version should have this fixed.
A workaround this is as follows
for form in formset:
if form.has_changed() and form not in formset.deleted_forms:
fields = form.changed_data
up_f = [field for field in fields if not form.fields[field].disabled]
if len(up_f) > 0:
updated_data.append(form.cleaned_data)
This results in updated_data having the only forms that are updated and not deleted.
I'm new to Django and I'm trying to implement a project where companies can register as users. As the companies may come from different countries and have different corporate forms they have some different mandatory fields when registering. The differences are only informational and have no consequence on the behaviour, i.e. all users should be treated equally after the registration. At the moment I only need to support 2 countries and 2 corporate forms but maybe more should be supported in the future.
I started with a single company model in form of a user-profile (one-to-one relationship with user) but in that case I have to set the fields which are different between company forms and countries as nullable and handle the correct input in the forms?
Furthermore I want to see all users (companies) in one single list in the admin.
How would you model that in Django?
Django form is very flexible in fields manipulation. You could always add/remove required from a field, or even delete a field from a form on the fly. All you need is to pass a parameter to the form constructor __init__(). Here's a basic example:
class BlahForm(forms.ModelForm):
class Meta:
model = Blah
fields = ('field1', 'field2',)
def __init__(self, *args, **kwargs):
param = kwargs.pop('param', None)
super(BlahForm, self).__init__(*args, **kwargs)
# how to mark a field as "not required"
if param == 'not_required':
self.fields['field1'].required = False
# here's how to delete a field
elif param == 'delete_field':
del self.fields['field1']
In the views.py you can initialize your form like this:
if 'condition1':
form = BlahForm(request.POST or None, param='not_required')
elif 'condition2':
form = BlahForm(required.POST or None, param='delete_field')
Your form would behave depends on that parameter you passed in, thus you only need one form for all countries(as you said the differences are trivial). Hope this helps.
one of the forms I need is a composite of simple fields (say "Department", "Building" and "RoomNumber"), and of dynamically generated pairs of fields (say "Name" and "Email"). Ideally, editing the contents of the simple fields and adding/removing dynamic field pairs would be done on a single form.
Code-wise, I'm wondering if trying to embed a Formset (of a form with the two dynamic fields) as a field in an ordinary form is a sensible approach or if there's another best practice to achieve what I'd like to accomplish.
Many thanks for any advice on these matters,
I'm not sure where the idea that you need to "embed a Formset as a field" comes from; this sounds like a case for the standard usage of formsets.
For example (making a whole host of assumptions about your models):
class OfficeForm(forms.Form):
department = forms.ModelChoiceField(...
room_number = forms.IntegerField(...
class StaffForm(forms.Form):
name = forms.CharField(max_length=...
email = forms.EmailField(...
from django.forms.formsets import formset_factory
StaffFormSet = formset_factory(StaffForm)
And then, for your view:
def add_office(request):
if request.method == 'POST':
form = OfficeForm(request.POST)
formset = StaffFormSet(request.POST)
if form.is_valid() && formset.is_valid():
# process form data
# redirect to success page
else:
form = OfficeForm()
formset = StaffFormSet()
# render the form template with `form` and `formset` in the context dict
Possible improvements:
Use the django-dynamic-formset jQuery plugin to get the probably-desired "add an arbitrary number of staff to an office" functionality without showing users a stack of blank forms every time.
Use model formsets instead (assuming the information you're collecting is backed by Django models), so you don't have to explicitly specify the field names or types.
Hope this helps.
Please this as a consideration question. Maybe somebody will use one of the
solutions below.
I have a couple of models which contain a ForeignKey(User) field.
My class-based create views are derived from the generic CreateView.
There are two options to save the associated user when adding a new object:
Saving the form in the views by overriding the form_valid method;
this doesn't expose user_id (and other not mentioned here data that should not be exposed)
class CreateOfferView(CreateView):
model = Offer
form_class = SomeModelFormWithUserFieldExcluded
def form_valid(self, form):
instance = form.save(commit=False)
instance.user = self.request.user
instance.save()
Saving the form with user id stored (and exposed) in a hidden field.
Here's the tricky part. There are more models with user field... so
when creating a form I need to fill the user field with initial (currently logged in) user and also I need to make that field hidden. For this purpose I've used my OwnFormMixin
class OwnFormMixin(object):
def get_form(self, form_class):
form = super(OwnFormMixin, self).get_form(form_class)
form.fields['user'].widget = forms.HiddenInput()
def get_initial(self):
initial = super(OwnFormMixin, self).get_initial()
initial['user'] = self.request.user.pk
#I could also do this in get_form() with form.fields['user'].initial
class CreateOfferView(OwnFormMixin, CreateView):
model = Offer
form_class = SomeModelFormWithAllFields
There are more CreateXXXView using the OwnFormMixin..
How do you save your user data in the forms?
Hidden vs. saving directly in your views? What are pros/cons?
Unless you're allowing users to modify that ForeignKeyField, there's no reason to include it in a form — I'd go with your first solution of using exclude to keep the user field out of your ModelForm, and setting the user from request.user. In fact, the Django documentation now has an example along these exact lines.
You have the advantage of not having to secure against manipulation of the user_id parameter, not exposing your internal user IDs and not having to worry about the different Create vs. Update cases. A slight disadvantage is that if you ever need the ability to change an object's associated User you'll need to start again.
I have a fairly simple Django application (v1.3 on Red Hat) for which I'm using the admin application to create and modify database records. One of the fields in my underlying model is a date field. Each time the corresponding field is displayed in the admin's new or edit form I'd like the initial value of this field to be today's date (and time). The user may choose to modify it thereafter, if she desires.
I know that I can set the default field value within my model definition (i.e. in models.py). Which works fine when a database record is first created. But for subsequent invocations of the change form the callable that I've assigned to the default parameter (datetime.datetime.now) obviously doesn't get invoked.
I've looked at - and tried - pretty well all of the many proposed solutions described elsewhere in stackoverflow, without success. Most of these appear to revolve around inserting initialisation code into the ModelForm subclass, e.g. either something like this...
class ConstantDefAdminForm(ModelForm) :
a_date_field = DateField(initial="datetime.datetime.now") # or now()
class Meta :
model = ConstantDef
widgets = {
...
}
or something like this...
class ConstantDefAdminForm(ModelForm) :
class Meta :
model = ConstantDef
widgets = {
...
}
def __init__(self, ...) :
# some initialisation of a_date_field
super(ConstantDefAdminForm, self).__init__(...)
But neither of these approaches work. The initial field value is always set to the value that is stored in the database. My reading of the Django documentation is that the various ways of imposing initial field values in forms only work for unbound forms, not bound forms. Right?
But this capability (to selectively override currently stored values) would seem to be such a popular requirement that I'm convinced that there must be a way to do it.
Has anyone out there succeeded in doing this?
Thanks in advance,
Phil
In Django 1.4 the default=<callable> in model's declaration works well:
class MyModel(models.Model):
dt = models.TimeField(null=True, blank=True, default=datetime.datetime.now)
every time you add a record the default value of the field is updated.
But the use the field's default parameter cause me some problem with the Admin log history of DateField objects, that are every time recorded as changed also when they are not modified. So I've adopted a solution based on https://stackoverflow.com/a/11145346/1838607:
import datetime
class MyModelAdminForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, *args, **kwargs):
super(MyModelAdminForm, self).__init__(*args, **kwargs)
self.fields['dt'].initial = datetime.datetime.now
class MyModelAdmin(admin.ModelAdmin):
form = MyModelAdminForm
fields = ('dt',)
Here's an approach that might work. In your model admin class, change the value of obj.a_date_field before the form is bound. The 'default' value for the date field should be the new value.
class MyModelAdmin(ModelAdmin):
...
def get_object(self, request, object_id):
obj = super(MyModelAdmin, self).get_object(request, object_id)
if obj is not None:
obj.a_date_field = datetime.now()
return obj
Note that get_object is not documented, so this is a bit hacky.
I had a similar problem, and I found the solution from here
I think what you will want to do is this:
class yourAdminModel(admin.ModelAdmin):
fields = ['your_date_field']
def add_view(self, request, form_url="", extra_context=None):
data = request.GET.copy()
data['your_date_field'] = datetime.date.today() # or whatever u need
request.GET = data
return super(yourAdminModel, self).add_view(request, form_url="", extra_context=extra_context)
You should be able to use auto_now with your DateTime Field which according to the docs will automatically set the value to now() each time the form is saved
Since Django 1.7 there is a function get_changeform_initial_data in ModelAdmin that sets initial form values:
def get_changeform_initial_data(self, request):
return {'dt': datetime.now()}