django modelchoicefield with unsaved models - python

All,
Is there a straightforward way for me to display a form that uses a modelchoicefield for models that have yet to be saved?
class Foo(models.Model):
name = models.CharField(max_length=42)
class Bar(models.Model):
name = models.CharField(max_length=42)
foo = models.ForeignKey("Foo",blank=True,null=True)
class BarForm(ModelForm):
class Meta:
model = Bar
pass
def create_new_bar(request):
foos = [Foo(name='foo1'), Foo(name='foo2'), Foo(name='foo3')]
bar = Bar(name='bar',foo=foos[0])
form = BarForm(instance=bar)
return render_to_response('bar_template.html',{"form" : form},context_instance=RequestContext(request))
But the "select" widget that gets rendered in the form has empty content. Presumably, this is because there is nothing to bind the queryset to for the ModelChoiceField that Bar's ForeignKey to Foo uses. (Since nothing in "foos" has been saved and therefore does not exist in the database).
Is there another way to deal with this besides writing a custom formfield/widget?
Thanks.

You can add additional field to BarForm with predefined choices - fooempty for example. That field will be shown in case if Foo table is empty. Try this (not tested):
class BarForm(ModelForm):
FOO_CHOICES = (
(0, 'Choice1'),
(1, 'Choice2'),
(2, 'Choice3'),
)
fooempty = forms.ChoiceField(required=False, label='lbl', choices=FOO_CHOICES)
class Meta:
model = Bar
def __init__(self, *args, **kwargs):
super(BarForm, self).__init__(*args, **kwargs)
if self.fields['foo'].queryset.count(): # Foo empty?
self.fields['fooempty'].widget = forms.HiddenInput()
else:
self.emptyflag
self.fields['foo'].widget = forms.HiddenInput()
def save(self, commit=True):
bar = super(BarForm, self).save(commit=False)
if self.emptyflag:
bar.foo_id = self.cleaned_data['fooempty']
bar.save()
return bar

EDIT: actually, now that I think about it. Those Foos won't have pks. So there is no way that could ever work. The form needs to know the pk of the Foos. You really just need to save them first. How would django know which one is which when it posts back the form? IGNORE ANSWER BELOW.
I think you can change the choices variable on the field. You will need to that in the form. So pass the list of Foos to the init of the form. Something like this:
class BarForm(ModelForm):
class Meta:
model = Bar
def __init__(self, foo_list, *args, **kwargs):
super(BarForm, self).__init__(*args, **kwargs)
self.fields['foo'].choices = foo_list
Then in the view:
foos = [Foo(name='foo1'), Foo(name='foo2'), Foo(name='foo3')]
bar = Bar(name='bar', foo=foos[0])
form = BarForm(foos, instance=bar)
I know I've done something like this before. I just can't remember the exact way. Something like this approach should work though.

Related

Restrict values that appear in Django admin select box

In Django 1.11, I have 2 models, Foo and Bar:
class Foo(models.Model):
name = models.CharField()
extra = models.BooleanField(default=False)
class Bar(models.Model):
name = models.CharField()
extra_foo = models.ForeignKey(Foo)
My admin.py looks like this:
class BarInline(admin.StackedInline):
model = Bar
fields = ('name', 'extra_foo')
class FooAdmin(admin.ModelAdmin):
fields('name')
inlines = [BarInline]
My problem is that the in the Bar inline form, the dropdown for extra_foo shows all of my existing Foos. I want it to only show Foos for which extra is true. How can I modify the admin to restrict the available options in a select box to a subset of the whole?
I guess, you can use render_change_form
class FooAdmin(admin.ModelAdmin):
def render_change_form(self, request, context, *args, **kwargs):
context['adminform'].form.fields['extra_foo'].queryset = Foo.objects.filter(extra=True)
return super(FooAdmin, self).render_change_form(request, context, *args, **kwargs)
admin.site.register(Foo, FooAdmin)
If you want this to be global then have a look at limit_choices_to
extra_foo = models.ForeignKey(Foo, limit_choices_to={'extra': True})
Credit : filter foreignkey field in django admin
You can achieve that by using limit_choices_to. Change this line:
extra_foo = models.ForeignKey(Foo)
to:
extra_foo = models.ForeignKey(Foo, limit_choices_to={'extra': True})
This will limit the choices for extra_foo to Foo objects that have the property extra set to True.
This approach will however limit the choices not only in the admin but also beyond. If you need to adjust only the admin, you need a different approach.

How to create a dynamic Django choice field

I am trying to generate a list to populate choices for a form. This list will be dynamic and will depend on what choices have been created by other users. Here is an example of the model the list should derive from:
#models.py
class User(models.Model):
brewery_you_work_for = models.CharField(choises=ALL_THE_BREWERIES_IN_THE_WORLD)
username = models.CharField()
I want the form to be something like this:
#forms.py
class BestBrewery(forms.Form):
vote = forms.ChoiceField(choices=BREWERIES_A_USER_WORKS_FOR)
What I want to do is have a list of all the breweries in the world for some users to select as the brewery they work for. Then I want to generate a list for other users to vote as the best brewery.
Lets say I have 3 users that claim they work the Sierra Nevada, 2 users that claim they work for Budweiser, and 5 users that claim they work for Coors.
I want to generate a list of breweries that would look like this:
(
'Budweiser',
'Coors,
'Sierra Nevada',
)
Note the alphabetical order and no repeated brewery listings.
You need to pass an extra user argument when initialising the form. Normally like this if you are using view functions:
form = BestBrewery(request.user)
If you are using Class Based Views you should override the get_form_kwargs method in your view:
def get_form_kwargs(self):
kwargs = super(MyView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
Your form should look like this:
class BestBrewery(forms.Form):
def __init__(self, user, *args, **kwargs):
super(BestBrewery, self).__init__(*args, **kwargs)
self.fields['vote'].choices = (
user.brewery_you_work_for,
user.get_brewery_you_work_for_display()
)
Just note that to override the choices field you need to provide tuples containing the value for the form and the text to be shown in the dropdown.
Well, I think u can set "choices" as result of function.
I tried like that:
def yolo():
# there we will get stats from db and return choices depends on stats
first_query = 1 # there U will use more complicated logic :)
second_query = 3
if first_query > second_query:
return (1, 2, 3)
else:
return (1, 3)
class Event(models.Model):
date = models.DateField(primary_key=True)
count_of_updates = models.SmallIntegerField(default=0)
yolos = models.CharField(max_length=255, choices=yolo())
And, for being sure U can check ur choices with something ugly:
choices = s._meta.get_field_by_name('yolos')[0].choices
print choices
Order and other things u can set in func logic :)
In your Form, you can change the choices of your field in the init.
class BestBrewery(forms.Form):
vote = forms.ChoiceField()
def __init__(self, *args, **kwargs):
super(BestBrewery, self).__init__(*arg, **kwargs)
# get the choices from where you need
choices = (...)
self.fields['vote'].choices = choices

How can I subclass a modelform (extend with extra fields from the model)?

I have a model and a form:
class MyModel(models.Model):
field_foo = models.CharField(_("Foo"), max_length=50)
field_bar = models.IntegerField(_("Bar"))
class MyFormOne(forms.ModelForm):
class Meta:
model=MyModel
fields = ('field_foo', )
widgets = {'field_foo': forms.TextInput(attrs={'size': 10, 'maxlength': 50}),}
I would like to have another form MyFormTwo which would subclass that form by including also the field field_bar. My point is not to have to repeat the widget declaration for field_foo in the second form (DRY principle), and also not to have to repeat the list of fields from MyFormOne (in reality there are much more then one field then in the simple example above).
How should I define MyFormTwo?
You shouldn't have to explicitly declare the entire widget, just modify the attrs which are different.
Or if you have custom widgets in the real code, I would either
Create a custom model field class as well which uses that widget by default, if it's a general condition, so in the form class it just works "automagically".
If it's just form specific (not model specific), then for that case I'd just declare the form field explicitly on Form class, not in Meta and then inheritance applies in a straightforward way.
But with default widgets (with custom attrs) I'd try something like the following
class MyModel(models.Model):
field_foo = models.CharField(_("Foo"), max_length=50)
field_bar = models.IntegerField(_("Bar"))
class MyFormOne(forms.ModelForm):
class Meta:
model=MyModel
fields = ('field_foo', )
def __init__(*args, **kwargs):
super(MyFormOne, self).__init__(*args, **kwargs)
self.fields['field_foo'].widget.attrs['size'] = 10
# max_length should already be set automatically from model
# TextInput is also default widget for CharField
class MyFormTwo(MyFormOne):
class Meta:
model=MyModel
fields = MyFormOne.Meta.fields + ('field_foo2',)
def __init__(*args, **kwargs):
super(MyFormTwo, self).__init__(*args, **kwargs)
self.fields['field_foo2'].widget.attrs['size'] = 10
I'm not sure if this will work or not (it's completely untested with Django forms/models), but if it does let me know.
class MyModel(models.Model):
field_foo = models.CharField(_("Foo"), max_length=50)
field_bar = models.IntegerField(_("Bar"))
class MyFormOne(forms.ModelForm):
class Meta:
model=MyModel
fields = ('field_foo', )
widgets = {'field_foo': forms.TextInput(attrs={'size': 10, 'maxlength': 50}),}
class MyFormTwo(MyFormOne):
class Meta(MyFormOne.Meta):
fields = MyFormOne.Meta.fields + ('field_foo2',)
widgets = MyFormOne.Meta.widgets
# Add new fields as a dictionary
widgets.update({'field_foo2': forms.TextInput(attrs={'size': 10, 'maxlength': 50}),)
# Or add them one by one
widgets['field_foo3'] = forms.TextInput(attrs={'size': 10, 'maxlength': 50})
Again, I have no idea if that will help you. Please let me know what results you get.
This snippet is a bit old, but it explains the only way I've ever been able to get form multiple inheritance to work. It's pretty ugly.
http://djangosnippets.org/snippets/703/
There is also an open ticket regarding it: https://code.djangoproject.com/ticket/7018

Django creating a form field that's read only using widgets

My form field looks something like the following:
class FooForm(ModelForm):
somefield = models.CharField(
widget=forms.TextInput(attrs={'readonly':'readonly'})
)
class Meta:
model = Foo
Geting an error like the following with the code above: init() got an unexpected keyword argument 'widget'
I thought this is a legitimate use of a form widget?
You should use a form field and not a model field:
somefield = models.CharField(
widget=forms.TextInput(attrs={'readonly': 'readonly'})
)
replaced with
somefield = forms.CharField(
widget=forms.TextInput(attrs={'readonly': 'readonly'})
)
Should fix it.
Note that the readonly attribute does not keep Django from processing any value sent by the client. If it is important to you that the value doesn't change, no matter how creative your users are with FireBug, you need to use a more involved method, e.g. a ReadOnlyField/ReadOnlyWidget like demonstrated in a blog entry by Alex Gaynor.
I was going into the same problem so I created a Mixin that seems to work for my use cases.
class ReadOnlyFieldsMixin(object):
readonly_fields =()
def __init__(self, *args, **kwargs):
super(ReadOnlyFieldsMixin, self).__init__(*args, **kwargs)
for field in (field for name, field in self.fields.iteritems() if name in self.readonly_fields):
field.widget.attrs['disabled'] = 'true'
field.required = False
def clean(self):
cleaned_data = super(ReadOnlyFieldsMixin,self).clean()
for field in self.readonly_fields:
cleaned_data[field] = getattr(self.instance, field)
return cleaned_data
Usage, just define which ones must be read only:
class MyFormWithReadOnlyFields(ReadOnlyFieldsMixin, MyForm):
readonly_fields = ('field1', 'field2', 'fieldx')
As Benjamin (https://stackoverflow.com/a/2359167/565525) nicely explained, additionally to rendering correctly, you need to process field on backend properly.
There is an SO question and answers that has many good solutions. But anyway:
1) first approach - removing field in save() method, e.g. (not tested ;) ):
def save(self, *args, **kwargs):
for fname in self.readonly_fields:
if fname in self.cleaned_data:
del self.cleaned_data[fname]
return super(<form-name>, self).save(*args,**kwargs)
2) second approach - reset field to initial value in clean method:
def clean_<fieldname>(self):
return self.initial[<fieldname>] # or getattr(self.instance, <fieldname>)
Based on second approach I generalized it like this:
from functools import partial
class <Form-name>(...):
def __init__(self, ...):
...
super(<Form-name>, self).__init__(*args, **kwargs)
...
for i, (fname, field) in enumerate(self.fields.iteritems()):
if fname in self.readonly_fields:
field.widget.attrs['readonly'] = "readonly"
field.required = False
# set clean method to reset value back
clean_method_name = "clean_%s" % fname
assert clean_method_name not in dir(self)
setattr(self, clean_method_name, partial(self._clean_for_readonly_field, fname=fname))
def _clean_for_readonly_field(self, fname):
""" will reset value to initial - nothing will be changed
needs to be added dynamically - partial, see init_fields
"""
return self.initial[fname] # or getattr(self.instance, fname)

Auto-generate form fields for a Form in django

I have some models and I want to generate a multi-selection form from this data.
So the form would contain an entry for each category and the choices would be the skills in that category.
models.py
class SkillCategory(models.Model):
name = models.CharField(max_length=50)
class Skill(models.Model):
name = models.CharField(max_length=50)
category = models.ForeignKey(SkillCategory)
Is there a way to auto-generate the form fields?
I know I can manually add a 'SkillCategory' entry in the form for each SkillCategory, but the reason to have it as a model is so skills and skillcategories can be edited freely.
I want to do something like this:
(I tried this, but didn't get it to work, don't remember the exact error...)
forms.py
class SkillSelectionForm(forms.Form):
def __init__(*args, **kwargs):
super(SkillSelectionForm, self).__init__(*args, **kwargs)
for c in SkillCategory.objects.all():
category_skills = [(pk, s.name) for s in c.skill_set.all()]
setattr(self, c.name, forms.MultipleChoiceField(choices=category_skills, widget=forms.CheckboxSelectMultiple))
SOLUTION
This creates a form field entry using the SkillCategory.name and assigns choices as those in Skill. field_name/display_name are used to avoid issues with non-ascii category names.
forms.py
def get_categorized_skills():
skills = {}
for s in Skill.objects.values('pk', 'name', 'category__name').order_by('category__name'):
if s['category__name'] not in skills.keys():
skills[s['category__name']] = []
skills[s['category__name']].append((s['pk'], s['name']))
return skills
class SkillSelectionForm(forms.Form):
def __init__(self, *args, **kwargs):
super(SkillSelectionForm, self).__init__(*args, **kwargs)
skills = get_categorized_skills()
for idx, cat in enumerate(skills.keys()):
field_name = u'category-{0}'.format(idx)
display_name = cat
self.fields[field_name] = forms.MultipleChoiceField(choices=skills[cat], widget=forms.CheckboxSelectMultiple, label=display_name)
Okay so you can't set fields like that on forms.Form, for reasons which will become apparent when you see DeclarativeFieldsMetaclass, the metaclass of forms.Form (but not of forms.BaseForm). A solution which may be overkill in your case but an example of how dynamic form construction can be done, is something like this:
base_fields = [
forms.MultipleChoiceField(choices=[
(pk, s.name) for s in c.skill_set.all()
]) for c in SkillCategory.objects.all()
]
SkillSelectionForm = type('SkillSelectionForm', (forms.BaseForm,), {'base_fields': base_fields})
Take a look at creating dynamic forms in Django, from b-list.org and uswaretech.com. I've had success using these examples to dynamically create form content from models.
What you want is a Formset. This will give you a set of rows, each of which maps to a specific Skill.
See the Formset documentation and the page specifically on generating formsets for models.

Categories