When working with Django model forms, I often do something like this:
def my_view(request):
new_entry = MyModel(name='a')
form = MyModelForm(instance=new_entry)
...
I want to do something similar with a modelformset. Something like this would be ideal:
def my_view(request):
MyFormSet = modelformset_factory(MyModel, form=MyModelForm)
new_entries = [MyModel(name='a'), MyModel(name='b')]
formset = MyFormSet(instances=new_entries) # off course this does not work
...
Since the items are not stored in the database yet, I can't set the instances using a queryset. If I want to use initial I have to declare the fields in the form, which is not ideal and seems a bit hacky.
Any suggestions how I can set the instances of each modelform in a modelformset?
Ok, I think I've found a solution.
class FormSetWithInstances(BaseFormSet):
def __init__(self, *args, **kwargs):
self.instances = kwargs.pop('instances')
super(FormSetWithInstances, self).__init__(*args, **kwargs)
def get_form_kwargs(self, index):
form_kwargs = super(FormSetWithInstances, self).get_form_kwargs(index)
if index < len(self.instances):
form_kwargs['instance'] = self.instances[index]
return form_kwargs
Be careful when using this modelformsets or inlinemodelformsets, as the queryset will override the instance you set.
An alternative approach:
class FormSetWithInstances(BaseFormSet):
def get_form_kwargs(self, index):
kwargs = super(FormSetWithInstances, self).get_form_kwargs(index)
instances = kwargs.pop('instances')
try:
kwargs.update({'instance': instances[index]})
except IndexError:
pass
return kwargs
Then, when creating instances of FormSetWithInstances, you pass in a list of instances as a form kwarg:
form_set = FormSetWithInstances(form_kwargs={'instances': [...]})
I personally prefer this method because it takes advantage of existing class infrastructure instead of defining custom class members in an overridden __init__(). Also, it's in the docs.
I'm not aware of an easy way to pass a list of instances as you are trying to do. Here's a couple of options that might work depending on your use case.
You can provide initial data for the model formset. This should be a list of dictionaries, not model instances:
initial = [{'name': 'a'}, {'name': 'b'}]
formset = MyFormSet(
queryset=MyModel.objects.none(),
initial=initial,
)
Note that I have set the queryset to an empty queryset. If you didn't do this, then the formset would display existing instances, and the initial data would be used for new instances.
If you have initial values for fields that you do not wish to include in the form, then you could be able to set those values when you [save the formset].
instances = formset.save(commit=False)
names = ['a', 'b']
for instance, name in zip(instances, names):
instance.name = name
instance.save()
Related
I have a view to list a certain model (lets call it class A), like this:
class BaseListView(ListView, MultipleObjectMixin):
http_method_names = ['get']
order_field = None
def get_paginate_by(self, queryset):
session_manager = SessionManager(self.request.session)
return session_manager.paginate_by.get()
def get_context_data(self, **kwargs):
context = super(BaseListView, self).get_context_data(**kwargs)
session_manager = SessionManager(self.request.session)
session_manager.paginate_by.set_to(context)
return context
This view did just what was needed, till now. Now I have to compare the list of objects it retrieves with another list of objects (class B).
The objects of class A and B both have a primary key with their name.
I want to check if any of the objects from class A has the same name (primary key) as any of the objects in class B. In case there is an instance of A in B I would like to add a certain parameter or something like is_in_B=True.
I need this so that I can represent these instances of A in a different way in the template.
How could I do this?
This is what I have come up with by myself for the moment:
class AListView(BaseListView):
model = "A"
def get_queryset(self):
queryset = super(AListView, self). get_query_set()
all_objects_A = A.objects.all()
all_objects_B = B.objects.all()
# modify queryset to indicate which instances of A are present in B
# No idea how to do this
return queryset
I'm not really sure this is an appropiate approach.
Also, how am I supposed to modify the queryset returned by my class so that I can indicate which instances of class A share the same name as any of the instances of class B?
You can annotate your queryset with a conditional expression to achieve this:
from django.db.models import Case, When, Value
def get_queryset(self):
# This gives you the queryset of A objects
queryset = super(AListView, self).get_queryset()
# List of primary keys of B objects
all_objects_B = B.objects.all().values_list('pk',flat=True)
# modify queryset to indicate which instances of A are present in B
return queryset.annotate(
is_in_b=Case(When(pk__in=all_objects_B, then=Value(True)),
default=Value(False))
)
)
Your queryset objects will now have an is_in_b property.
This will work fine if your list of B objects is small. If it is large then I am not sure it is very efficient, and you may need to develop this further to see whether the check (is A in B) can be done directly in the database (possibly requiring raw SQL).
So I have a serializer that looks like this
class BuildingsSerializer(serializers.ModelSerializer):
masterlisting_set = serializers.PrimaryKeyRelatedField(many=True,
queryset=Masterlistings.objects.all())
and it works great
serializer = BuildingsSerializer(Buildings.objects.get(pk=1))
serializer.data
produces
OrderedDict([
("masterlistings_set", [
"0a06e3d7-87b7-4526-a877-c10f54fa5bc9",
"343643ac-681f-4597-b8f5-ff7e5be65eef",
"449a3ad2-c76c-4cb8-bb86-1be72fafcf64",
])
])
but if I change the queryset in the serializer to
class BuildingsSerializer(serializers.ModelSerializer):
masterlistings_set = serializers.PrimaryKeyRelatedField(many=True, queryset=[])
I still get the same exact result back.
OrderedDict([
("masterlistings_set", [
"0a06e3d7-87b7-4526-a877-c10f54fa5bc9",
"343643ac-681f-4597-b8f5-ff7e5be65eef",
"449a3ad2-c76c-4cb8-bb86-1be72fafcf64",
])
])
Is this supposed to be happening? Am I using querysets incorrectly?
I used [] as an easy example to show that no matter what I put in nothing changes.
Please any insight would be invaluable
It should be noted that masterlistings has a primary key relationship that points to buildings. So a masterlisting belong to a building.
As pointed out by #zymud, queryset argument in PrimaryKeyRelatedField is used for validating field input for creating new entries.
Another solution for filtering out masterlistings_set is to use serializers.SerializerMethodField() as follows:
class BuildingsSerializer(serializers.ModelSerializer):
masterlisting_set = serializers.SerializerMethodField()
def get_masterlisting_set(self, obj):
return MasterListing.objects.filter(building=obj).values_list('pk',flat=True)
queryset in related field limits only acceptable values. So with queryset=[] you will not be able to add new values to masterlisting_set or create new Buildings.
UPDATE. How to use queryset for filtering
This is a little bi tricky - you need to rewrite ManyRelatedField and many_init method in your RelatedField.
# re-define ManyRelatedField `to_representation` method to filter values
# based on queryset
class FilteredManyRelatedField(serializers.ManyRelatedField):
def to_representation(self, iterable):
iterable = self.child_relation.queryset.filter(
pk__in=[value.pk for value in iterable])
return super(FilteredManyRelatedField, self).to_representation(iterable)
# use overridden FilteredManyRelatedField in `many_init`
class FilteredPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
#classmethod
def many_init(cls, *args, **kwargs):
kwargs['child_relation'] = cls(queryset=kwargs.pop('queryset'))
return FilteredManyRelatedField(*args, **kwargs)
Is there any way to strip surrounding whitespace from all values in WTForms without adding a filter to every single field?
Currently I'm passing filters=[strip_whitespace] with the function shown below to my fields but having to repeat this for every field is quite ugly.
def strip_whitespace(s):
if isinstance(s, basestring):
s = s.strip()
return s
A solution requiring subclassing of Form would be fine since I'm already doing that in my application.
You can do it in WTForms 2.x by using the bind_field primitive on class Meta. The class Meta paradigm is a way to override WTForms behaviors in contexts such as binding/instantiating fields, rendering fields, and more.
Because anything overridden in class Meta defined on a Form is inherited to any form subclasses, you can use it to set up a base form class with your desired behaviors:
class MyBaseForm(Form):
class Meta:
def bind_field(self, form, unbound_field, options):
filters = unbound_field.kwargs.get('filters', [])
filters.append(my_strip_filter)
return unbound_field.bind(form=form, filters=filters, **options)
def my_strip_filter(value):
if value is not None and hasattr(value, 'strip'):
return value.strip()
return value
Now, just inherit MyBaseForm for all your forms and you're good to go.
Unfortunately, I have no enough reputation to comment first response.
But, there is extremely unpleasant bug in that example:
When you do filters.append(smth) then on each form initialization filters growth by 1 element.
As a result, your code works slower and slower until you restart it
Consider Example:
class MyBaseForm(Form):
class Meta:
def bind_field(self, form, unbound_field, options):
filters = unbound_field.kwargs.get('filters', [])
filters.append(my_strip_filter)
return unbound_field.bind(form=form, filters=filters, **options)
def my_strip_filter(value):
if value is not None and hasattr(value, 'strip'):
return value.strip()
return value
class MyCustomForm(MyBaseForm):
some_field = StringField(filters=[lambda x: x])
for i in range(100):
MyCustomForm(MultiDict({'some_field': 'erer'}))
print(len(MyCustomForm.some_field.kwargs['filters'])) # print: 101
So the fast fix is to check that this filter not in list:
class MyBaseForm(Form):
class Meta:
def bind_field(self, form, unbound_field, options):
filters = unbound_field.kwargs.get('filters', [])
if my_strip_filter not in filters:
filters.append(my_strip_filter)
return unbound_field.bind(form=form, filters=filters, **options)
I wouldn't be surprised if you could do it by subclassing form, but my solution was to just create custom Stripped* fields. I think this is at least better than passing filters every time because it is less error prone:
from wtforms import StringField, PasswordField
class Stripped(object):
def process_formdata(self, valuelist):
if valuelist:
self.data = valuelist[0].strip()
else:
self.data = ''
class StrippedStringField(Stripped, StringField): pass
class StrippedPasswordField(Stripped, PasswordField): pass
I am trying to generate a form in WTForms that has dynamic fields according to this documentation http://wtforms.simplecodes.com/docs/1.0.2/specific_problems.html#dynamic-form-composition
I have this subform class which allows users to pick items to purchase from a list:
class Item(Form):
itmid = SelectField('Item ID')
qty = IntegerField('Quantity')
class F(Form):
pass
There will be more than one category of shopping items, so I would like to generate a dynamic select field based on what categories the user will choose:
fld = FieldList(FormField(Item))
fld.append_entry()
but I get the following error:
AttributeError: 'UnboundField' object has no attribute 'append_entry'
Am I doing something wrong, or is there no way to accomplish this in WTForms?
I ran into this issue tonight and ended up with this. I hope this helps future people.
RecipeForm.py
class RecipeForm(Form):
category = SelectField('Category', choices=[], coerce=int)
...
views.py
#mod.route('/recipes/create', methods=['POST'])
def validateRecipe():
categories = [(c.id, c.name) for c in g.user.categories.order_by(Category.name).all()]
form = RecipeForm(request.form)
form.category.choices = categories
...
#mod.route('/recipes/create', methods=['GET'])
def createRecipe():
categories = [(c.id, c.name) for c in g.user.categories.order_by(Category.name).all()]
form = RecipeForm(request.form)
form.category.choices = categories
return render_template('recipes/createRecipe.html', form=form)
I found this post helpful as well
class BaseForm(Form):
#classmethod
def append_field(cls, name, field):
setattr(cls, name, field)
return cls
from forms import TestForm
form = TestForm.append_field("do_you_want_fries_with_that",BooleanField('fries'))(obj=db_populate_object)
I use the extended class BaseForm for all my forms and have a convenient append_field function on class.
Returns the class with the field appended, since instances (of Form fields) can't append fields.
Posting without writing full code or testing the code, but maybe it will give you some ideas. Also this could maybe only help with the filling the needed data.
You need to fill choices for SelectField to be able to see the data and be able to select it. Where you fill that? Initial fill should be in the form definition, but if you like dynamic one, I would suggest to modify it in the place where you creating this form for showing to the user. Like the view where you do some form = YourForm() and then passing it to the template.
How to fill form's select field with choices? You must have list of tuples and then something like this:
form.category_select.choices = [(key, categories[key]) for key in categories]
form.category_select.choices.insert(0, ("", "Some default value..."))
categories here must be dictionary containing your categories in format like {1:'One', 2:'Two',...}
So if you will assign something to choices when defining the form it will have that data from the beginning, and where you need to have user's categories, just overwrite it in the view.
Hope that will give you some ideas and you can move forward :)
have you tried calling append_entry() on the form instance instead of the FieldList definition?
class F(Form)
fld = FieldList(SelectField(Item))
form = F()
form.fld.append_entry()
This is how i got it to work.
class MyForm(FlaskForm):
mylist = SelectField('Select Field', choices=[])
#app.route("/test", methods=['GET', 'POST']
def testview():
form = MyForm()
form.mylist.choices = [(str(i), i) for i in range(9)]
Strangely this whole thing stops working for me if i use coerce=int. I am myself a flask beginner, so i am not really sure why coerce=int causes issue.
WTForms Documentation : class wtforms.fields.SelectField
Select fields with dynamic choice values:
class UserDetails(Form):
group_id = SelectField(u'Group', coerce=int)
def edit_user(request, id):
user = User.query.get(id)
form = UserDetails(request.POST, obj=user)
form.group_id.choices = [(g.id, g.name) for g in Group.query.order_by('name')]
I have a baseform with over 20 fields. Then I have about 15 other forms inheriting from that form, passing in a parameter called fields which the baseform uses to delete all other fields. Best explain via example:
class BaseForm(forms.Form):
reportid = forms.HiddenInput()
fromdate = forms.DateField(label=_("From"), widget=widgets.AdminDateWidget())
todate = forms.DateField(label=_("To"), widget=widgets.AdminDateWidget())
sort_by = forms.ChoiceField(label=_("Sort by"), choices=[])
.......
def __init__(self, *args, **kwargs):
fields = kwargs.pop('fields')
#Pseudo:
***del self.fields[field] for field not in fields***
class SubForm(forms.Form):
def __init__(self, *args, **kwargs):
fields = ['reportid', 'todate']
super(SubForm, self).__init__(fields=fields, *args, **kwargs)
The resulting form would then look like this:
class SubForm(forms.Form):
reportid = forms.HiddenInput()
todate = forms.DateField(label=_("To"), widget=widgets.AdminDateWidget())
My problem is that when the BaseForm is initialized for the first time, the labels are bound to the fields with the active language, and when another user logs in with another language setting (or the current user changes languages) the field labels don't update.
I've come to a solution using a dict like this:
labels = {
'todate': lambda: _("To"),
'fromdate': lambda: _("From"),
.....
}
and then when initializing the baseform looping through all fields and setting
self.fields[field].widget.label = labels[field]()
Do I have any nicer (read: more pythonic) way of achieving this?
Django provides _lazy variants of the translation functions (for example ugettext_lazy) so you can ark strings for translations at the access time (as opposed to when the translation function is called).
It's documented in details at https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#lazy-translation