I am using django-haystack with Solr backend in one of my projects. I have a SearchForm which inherits from FacetedSearchForm. Now, what I am trying to do is to add an intital value to some of the fields within the Form.
from django.forms import forms
from haystack.forms import FacetedSearchForm
MySearchForm(FacetedSearchForm):
""" My basic search form
"""
field_one = forms.CharField(required=False, initial='0')
field_two = forms.CharField(required=False, initial='some_value')
previously when i was using django.forms.Form this used to work absolutely fine. I have been digging around django code and haystack code and fond that FacetedSearchForm extends haystack.forms.SearchForm which extends django.forms.Forms. So I can not see a reason why this should not work.
References:
haystack.forms.FacetedSearchForm
django.forms.Form
I also tried overriding the __init__ method and thought I would set the initial values there:
def __init__(self, *args. **kwargs):
data = kwargs.pop('data', None)
if data:
values = {
'field_one': (data.get('field_one', None), '0'),
'field_two': (data.get('field_two', None), 'some_value')
}
for value in values:
if not values[value][0]:
self.fields[value].initial = values[value][3]
super(MySearchForm, self).__init__(*args, **kwargs)
but in a FacetedSearchForm there is no fields attribute, though they provide access to base_fields object, so I instead tried doing:
self.base_fields[value].initial = values[value][4]
which does not work either.
Strange part came when I realized that It does not raise any errors or exceptions, so I tried printing out the initial values to debug and well it does show that inital values are set.
But when the form is rendered in the template the fields does not have those initial values. The values for the fields are None for both fields in the template.
I tried checking the value using:
{{ form.field_one.value }}
{{ form.field_two.value }}
and the output is:
None
None
Have anyone ran into this issue before ? How can I have inital values show up in the template when the form is rendered ?
As django.forms.Form set initial data only to an unbound form setting inital is not an option here. We should rather pass the inital values as data to the build_form method in haystack.views.FacetedSearchView as this is where form is initialized.
Example:
MySearchView(FacetedSearchView):
def build_form(self, form_kwargs=None):
""" Overrides the build_form method to send initial data
"""
form = super(MySearchForm, self).build_form(form_kwargs)
form_data = form.data.copy() # Need to copy this as QueryDict is immutable
# Set initial data hete
form_data['field_name'] = 'fields_inital_value'
form.data = form_data
return form
Related
In Django's admin, I've seen how I can set the fields of an 'add object' form using GET variables (e.g. /admin/app/model/add?title=lol sets the 'Title' field to 'lol').
However, I want to be able to do something along the lines of /admin/app/model/add?key=18 and load default data for my fields from an instance of another model. Ideally, I'd also like to be able to do some processing on the values that I populate the form with. How do I do this?
I managed to figure it out. Thankfully, Django allows you to replace a request's GET dict (which it uses to pre-populate the admin form). The following worked for me:
class ArticleAdmin(admin.ModelAdmin):
# ...
def add_view(self, request, form_url='', extra_context=None):
source_id = request.GET.get('source', None)
if source_id is not None:
source = FeedPost.objects.get(id=source_id)
# any extra processing can go here...
g = request.GET.copy()
g.update({
'title': source.title,
'contents': source.description + u"... \n\n[" + source.url + "]",
})
request.GET = g
return super(ArticleAdmin, self).add_view(request, form_url, extra_context)
This way, I obtain the source object from a URL parameter, do what I want with them, and pre-populate the form.
You can override method add_view of ModelAdmin instance. Add getting an object there, set object's pk to None and provide that object as an instance to the form. Object with pk == None will be always inserted as a new object in the database on form's save()
I have a model with a field for a json object. This object is used on the site to control some css variables, among other things.
Right now in the admin, I have a text field where a user can save a json object. I'd like to show a form with all the attributes that, upon saving, will generate a json object.
Basically, the user sees, and the data is stored, like this:
{
"name":"hookedonwinter",
"user-id":123,
"basics":{
"height":150,
"weight":150
}
}
And I'd rather have the user see this:
Name: <input field>
User Id: <input field>
Height: <input field>
Weight: <input field>
and the data still be stored in json.
Any guidance would be appreciated. Links to docs that explain this, doubly appreciated.
Thanks!
Idea
Basically what you need to do is render your JSON into fields.
Create field for your model that stores JSON data.
Create form field
Create widget that:
Renders fields as multiple inputs
Takes data from POST/GET and transforms it back into JSON
You can also skip steps 1, 2 by overriding widget for TextField.
Documentation links
Widgets: https://docs.djangoproject.com/en/1.3/ref/forms/widgets/
Django code for reference how to create widgets: https://code.djangoproject.com/browser/django/trunk/django/forms/widgets.py
Proof of concept
I tried coding this solution and here is solution that worked for me without some edge cases.
fields.py
import json
from django.db import models
from django import forms
from django import utils
from django.utils.translation import ugettext_lazy as _
class JSONEditableField(models.Field):
description = _("JSON")
def formfield(self, **kwargs):
defaults = {'form_class': JSONEditableFormField}
defaults.update(kwargs)
return super(JSONEditableField, self).formfield(**defaults)
class JSONEditableWidget(forms.Widget):
def as_field(self, name, key, value):
""" Render key, value as field """
attrs = self.build_attrs(name="%s__%s" % (name, key))
attrs['value'] = utils.encoding.force_unicode(value)
return u'%s: <input%s />' % (key, forms.util.flatatt(attrs))
def to_fields(self, name, json_obj):
"""Get list of rendered fields for json object"""
inputs = []
for key, value in json_obj.items():
if type(value) in (str, unicode, int):
inputs.append(self.as_field(name, key, value))
elif type(value) in (dict,):
inputs.extend(self.to_fields("%s__%s" % (name, key), value))
return inputs
def value_from_datadict(self, data, files, name):
"""
Take values from POST or GET and convert back to JSON..
Basically what this does is it takes all data variables
that starts with fieldname__ and converts
fieldname__key__key = value into json[key][key] = value
TODO: cleaner syntax?
TODO: integer values don't need to be stored as string
"""
json_obj = {}
separator = "__"
for key, value in data.items():
if key.startswith(name+separator):
dict_key = key[len(name+separator):].split(separator)
prev_dict = json_obj
for k in dict_key[:-1]:
if prev_dict.has_key(k):
prev_dict = prev_dict[k]
else:
prev_dict[k] = {}
prev_dict = prev_dict[k]
prev_dict[dict_key[-1:][0]] = value
return json.dumps(prev_dict)
def render(self, name, value, attrs=None):
# TODO: handle empty value (render text field?)
if value is None or value == '':
value = '{}'
json_obj = json.loads(value)
inputs = self.to_fields(name, json_obj)
# render json as well
inputs.append(value)
return utils.safestring.mark_safe(u"<br />".join(inputs))
class JSONEditableFormField(forms.Field):
widget = JSONEditableWidget
models.py
from django.db import models
from .fields import JSONEditableField
class Foo(models.Model):
text = models.TextField()
json = JSONEditableField()
Hope this helps and here is how it looks:
I had similar task. I resolved it by creating Django form widget. You can try it for yours applications django-SplitJSONWidget-form
Interesting question! I'd like to see good and elegant solution for it :)
But it seems to me, that django-admin is not suitable for your task. I'd try to play with Forms. Smth like this:
class HmmForm(forms.Form):
name = forms.CharField(max_length = 128)
user_id = forms.IntegerField()
height = forms.IntegerField()
weight = forms.IntegerField()
def test(request, pk):
form = HmmForm()
if pk > 0:
hmm = Hmm.objects.get(pk = pk)
form = HmmForm( initial = {"name": hmm.name} )
return render_to_response("test/test.html", {"form": form})
And then simple render form in template, as you wish:
{{ form.as_table }} or {{ form.as_p }}
It's looks simple like this:
#Creating custom form
class MyCoolForm(forms.ModelForm):
class Meta:
model = MyModel
exclude = ('field_that_stores_json', )
#field_that_shows_json1 = forms.CharField()
#field_that_shows_jsons = forms.CharField()
def __init__(self, *args, **kwargs):
#Deserizlize field that stores json here
def save(self, *args, **kwargs):
#Serialize fields that shows json here
After all, just set this form as a form for admin.
P.S.: Also you can write your own widget for form, that transforms json object into fields on js level and has textarea underneath.
Basically it sounds like you want a custom widget for your text field. The snippet on this page gives an example on how to render json key-value pairs. Even if it doesn't suit your needs entirely, especially as your nested json adds some complexity, it perhaps can give you some ideas.
As for the pure storage and retrieval of json objects into Python dicts, a few reusable JSONField implementations exist, like this one. You might want to add it to the mix.
Try using YAML as the format for user input, and then deserialize the object and serialize it back to json in the back end. Django already has serializers for that.
django-submodel may help you, although it cannot represent layered key-value now.
It's a pity to miss such HUGE bounty =p
I'm wondering how to change the behavior of a form field based on data in the request... especially as it relates to the Django Admin. For example, I'd like to decrypt a field in the admin based on request data (such as POST or session variables).
My thoughts are to start looking at overriding the change_view method in django/contrib/admin/options.py, since that has access to the request. However, I'm not sure how to affect how the field value displays the field depending on some value in the request. If the request has the correct value, the field value would be displayed; otherwise, the field value would return something like "NA".
My thought is that if I could somehow get that request value into the to_python() method, I could directly impact how the field is displayed. Should I try passing the request value into the form init and then somehow into the field init? Any suggestions how I might approach this?
Thanks for reading.
In models.py
class MyModel(models.Model):
hidden_data = models.CharField()
In admin.py
class MyModelAdmin(models.ModelAdmin):
class Meta:
model = MyModel
def change_view(self, request, object_id, extra_context=None):
.... # Perhaps this is where I'd do a lot of overriding?
....
return self.render_change_form(request, context, change=True, obj=obj)
I haven't tested this, but you could just overwrite the render_change_form method of the ModelAdmin to sneak in your code to change the field value between when the change_view is processed and the actual template rendered
class MyModelAdmin(admin.ModelAdmin):
...
def render_change_form(self, request, context, **kwargs):
# Here we have access to the request, the object being displayed and the context which contains the form
form = content['adminform'].form
field = form.fields['field_name']
...
if 'obj' in kwargs:
# Existing obj is being saved
else:
# New object is being created (an empty form)
return super(MyModelAdmin).render_change_form(request, context, **kwargs)
I have a Django ModelForm in Google App Engine with a ChoiceField, let's say location:
class MyForm(ModelForm):
location = ChoiceField(label="Location")
class Meta:
model = MyModel
In order to dynamically add the choices for location, and not have issues with app caching, I add them after the form has initialized:
form = MyForm(request.POST, instance=my_instance)
form.fields['location'].choices = Location.all().fetch(1000)
The problem I'm having now is that when the form is initialized via the data in request.POST the choices do not yet exist and I am receiving an error stating that an invalid choice is made (since the value does not yet exist in the list of choices).
I don't like that validation is occurring when I am initializing the form instead of waiting until I call form.is_valid(). Is there any way to suppress validation during my object instantiation? Or some other way to fix this?
UPDATE: I'm pretty sure ModelFormMetaclass is causing me my grief by validating the provided instance when the form is created. Still not sure how to fix though.
Thanks!
There must be other ways to do this, but possibly the most straightforward is to add the field in the form's __init__() method:
class MyForm(ModelForm):
...
def __init__(self, *args, **kwargs):
try:
dynamic_choices = kwargs.pop('dynamic_choices')
except KeyError:
dynamic_choices = None # if normal form
super(MyForm, self).__init__(*args, **kwargs)
if dynamic_choices is not None:
self.fields['location'] = ModelChoiceField(
queryset=dynamic_choices)
class Meta:
model = MyModel
And your view would look something like:
def my_view(request):
locations = Location.objects.all() # or filter(...) or whatever
dynamic_form = MyForm(dynamic_choices=locations)
return direct_to_template(request,
'some_page.html',
{'form': dynamic_form},)
Let us know how that works for you.
Is it possible to prepopulate a formset with different data for each row? I'd like to put some information in hidden fields from a previous view.
According to the docs you can only set initial across the board.
If you made the same mistake as me, you've slightly mistaken the documentation.
When I first saw this example...
formset = ArticleFormSet(initial=[
{'title': 'Django is now open source',
'pub_date': datetime.date.today(),}
])
I assumed that each form is given the same set of initial data based on a dictionary.
However, if you look carefully you'll see that the formset is actually being passed a list of dictionaries.
In order to set different initial values for each form in a formset then, you just need to pass a list of dictionaries containing the different data.
Formset = formset_factory(SomeForm, extra=len(some_objects)
some_formset = FormSet(initial=[{'id': x.id} for x in some_objects])
You need to use the technique described in this post in order to be able to pass parameters in. Credit to that author for an excellent post. You achieve this in several parts:
A form aware it is going to pick up additional parameters
Example from the linked question:
def __init__(self, *args, **kwargs):
someeobject = kwargs.pop('someobject')
super(ServiceForm, self).__init__(*args, **kwargs)
self.fields["somefield"].queryset = ServiceOption.objects.filter(
somem2mrel=someobject)
Or you can replace the latter code with
self.fields["somefield"].initial = someobject
Directly, and it works.
A curried form initialisation setup:
formset = formset_factory(Someform, extra=3)
formset.form = staticmethod(curry(someform, somem2mrel=someobject))
That gets you to passing custom form parameters. Now what you need is:
A generator to acquire your different initial parameters
I'm using this:
def ItemGenerator(Item):
i = 0
while i < len(Item):
yield Item[i]
i += 1
Now, I can do this:
iterdefs = ItemGenerator(ListofItems) # pass the different parameters
# as an object here
formset.form = staticmethod(curry(someform, somem2mrel=iterdefs.next()))
Hey presto. Each evaluation of the form method is being evaluated in parts passing in an iterated parameter. We can iterate over what we like, so I'm using that fact to iterate over a set of objects and pass the value of each one in as a different initial parameter.
Building on Antony Vennard's answer, I am not sure what version of python/django he is using but I could not get the generator to work in the curry method either. I am currently on python2.7.3 and django1.5.1. Instead of using a custom Generator, I ended up using the built-in iter() on a list of things to create an iterator and passing the iterator itself in the curry method and calling next() on it in the Form __init__(). Here is my solution:
# Build the Formset:
my_iterator = iter(my_list_of_things) # Each list item will correspond to a form.
Formset = formset_factory(MyForm, extra=len(my_list_of_things))
Formset.form = staticmethod(curry(MyForm, item_iterator=my_iterator))
And in the form:
# forms.py
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
# Calling next() on the iterator/generator here:
list_item = kwargs.pop('item_iterator').next()
# Now you can assign whatever you passed in to an attribute
# on one of the form elements.
self.fields['my_field'].initial = list_item
Some Key things I found were that you need to either specify an 'extra' value in the formset_factory or use the initial kwarg on the formset to specify a list that corresponds to the list you pass to the iterator (In above example I pass the len() of the my_list_of_things list to 'extra' kwarg to formset_factory). This is necessary to actually create a number of forms in the formset.
I had this problem and I made a new widget:
from django.forms.widgets import Select
from django.utils.safestring import mark_safe
class PrepolutatedSelect(Select):
def render(self, name, value, attrs=None, choices=()):
if value is None: value = ''
if value == '':
value = int(name.split('-')[1])+1
final_attrs = self.build_attrs(attrs, name=name)
output = [u'<select%s>' % flatatt(final_attrs)]
options = self.render_options(choices, [value])
if options:
output.append(options)
output.append('</select>')
return mark_safe(u'\n'.join(output))
Maybe this will work for you too.
formset = BookFormset(request.GET or None,initial=[{'formfield1': x.modelfield_name1,'formfield2':x.modelfield_name2} for x in model])
formfield1,formfield2 are the names of the formfields.
modelfield_name1,modelfield_name2 are the modal field names.
model is name of your modal class in models.py file.
BookFormset is the form or formset name which is defined in your forms.py file