Create multiple forms on the same page - python

I am trying to figure out how to create many forms that are similar on one page. The idea is to have people comment on various sections of a page with text. This means that,
each section has its comments. The forms are the same with a comment field and a submit button.
This is the code i got so far. However it does not work and i definitely need to understand how this can be achieved in a simple way. Can somebody help me figure this out.
In model:
db.define_table('manyforms',
Field('comment'))
In controller:
allforms=dict(form_id=new_form_id(),form=SQLFORM(db.manyforms))
for f in allforms:
if f['form'].accepts(request.vars,formname=f['form_id']):
response.flash = 'accepted'
else:
response.flash = 'refused'
return dict(allforms=allforms)

First of all, this is a useful reference: Multiple forms per page (web2py documentation)
Also, in your function, you are iterating through a dict of two items of different types. You are trying to treat each of those objects (one of which will presumably be an integer, the other, a form) as some object that is indexed by a string key (such as a dict).
Finally, it is not clear from your post what you want your function to do. Does your function correspond to a view? If so, do you really want a bunch of forms that are accessed in your view by a single variable {{=allforms}}? Perhaps from an engineering point of view, a better route is to first create a function that generates a list of forms; maybe something like this:
def generate_forms():
form_id_list = # create id list according to whatever criteria
forms = [SQLFORM(db.manyforms, _id = _id) for _id in form_id_list]
for form in forms:
if form.process(request.vars, formname = form['_id']).accepted:
response.flash = 'accepted'
else:
response.flash = 'refused'
return forms

Related

(Django) How to clean an unbound form?

I have a problem. I looked at the web for a solution but did not find one. If my problem already have an answer, please give me the links.
The Problem
Here is the problem:
I create a form inside one of my view with some initial values:
form = MyForm(initial=initial)
However, I do not have a full control over these initial values, thus I need to check is the form is valid. However, because the form in unbound .is_valid() always return False.
So, I tried to create an bound form from the first step:
form = MyForm(initial)
However, I do not initialize all the field, simply because I do not know their name and how many they are, thus I cannot initialize all of them.
The problem is that some of the field I do not initialize are required. Thus, the .is_valid() always return False because I do not provide required field(s).
What I know is that all of the required field have a default value.
When I created the form with initial values (i.e. MyForm(initial=initial)), the defaults value are provided. So I wish I could do something like:
form = MyForm(initial=initial)
form = MyForm(form)
However, that obviously doesn't work.
I see two potential solution to my problem:
Validate an unbound form
Get a list with all the fields and their default values (if one) (I do not know this list in advance)
I do not know how to make 2, however, I tried this for 1:
form = MyForm(initial=initial)
form.clean()
form.clean() calls a custom function where I stated (I had no problem with a bound form):
cleaned_date = super().clean()
That returns the following errors:
AttributeError at XXX
'MyForm' object has no attribute 'cleaned_data'
And well, that is kind of logical, the form is unbound, thus the data are not cleaned yet.
Any piece of advice (or complete solution) will be greatly appreciated.
Thank you for having reading me.
Wrong Solution (that works, but too wordly/ugly)
There is one solution to this problem but I am sure this is not the could one (it is way too ugly).
I first create a unbound field:
form = MyForm()
Then I read the initial value of the fields inside the string representation of the form.
for field in form:
index_name = str(field.find("name"
name = field[index_name+len('name="':]
name = name[:name.find('"')]
index = str(field).find("value")
if index >= 0: # if their is a default value
value = field[index+len('value="'):]
value = value[:value.find('"')]
initial[name] = value
Then I just need to remake the form, bound this time:
form = MyForm(initial)
However, this solution is overwhelming and I am sure there is a better way to go.
Ideally you would get the default values and do an initial.update(defaults) and hand it to the form.
If the defaults are not available to you, you can try to remove fields from the form that are not in initial.
form = MyForm(data=initial)
field_keys = list(form.fields.keys())
for field_name in field_keys:
if field_name not in initial:
form.fields.pop(field_name)
form.is_valid() # <- only validates fields in initial
Maybe you can initialize your form with your initial values with no full control, and run form.is_valid() and use the form.cleaned_data to initial another form? But Why would you have to do validating on the unbound form? I think this scenario is rare.

Django Add list generated from the text of one field to many to many field

Having a bit of trouble trying to bulk add a list of items to a many to many field and though having tried various things have no clue on how to approach this. I've looked at the Django documentation and cant seem to find what I'm looking for.
Here is the code for my models:
class Subject(models.Model):
noun = models.CharField(max_length=30, null=True, blank=True)
class Knowledge(models.Model):
item_text = models.TextField()
item_subjects = models.ManyToManyField(Subject, null=True, blank=True)
def add_subjects(sender, instance, *args, **kwargs):
if instance.item_info:
item_subjects = classifier.predict_subjects(instance.item_info)
if item_subjects:
....
post_save.connect(add_subjects, sender=Knowledge)
The list is being generated by the classifer.predict_subjects function.
I have tried using the m2m_changed connector and the pre_save and post_save connect. I'm not even sure the many to many field is the right option would it be better to do make a foreign key relationship.
in place of the '...' I have tried this but it doesn't create the relationship between and only saves the last one.
for sub in item_subjects:
subject = Subject(id=instance.id, noun=sub)
subject.save()
I've also tried
instance.item_subjects = item_subjects
and a load more things that I can't really remember, I don't really think I'm in the right ballpark to be honest. Any suggestions?
edit:
ok, so I have got it adding all of the list items but still haven't managed to link these items to the many to many field.
for sub in item_subjects:
subject = Subject.objects.get_or_create(noun=sub)
edit 2:
So doing pretty much exactly the same thing outside of the loop in the Django shell seems to be working and saves the entry but it doesn't inside the function.
>>> k[0].item_subjects.all()
<QuerySet []>
>>> d, b = Subject.objects.get_or_create(noun="cats")
<Subject: cats>
>>> k[0].item_subjects.add(d)
>>> k[0].item_subjects.all()
<QuerySet [<Subject: cats>]>
edit 3
So I took what Robert suggested and it works in the shell just like above just not when using it in the admin interface. The print statements in my code show the array item being updated but it just dosen't persist. I read around and this seems to be a problem to do with the admin form clearing items before saving.
def sub_related_changed(sender, instance, *args, **kwargs):
print instance.item_subjects.all()
if instance.item_info:
item_subjects = classifier.predict_subjects(instance.item_info)
if item_subjects:
for sub in item_subjects:
subject, created = Subject.objects.get_or_create(noun=sub)
instance.item_subjects.add(subject)
print instance.item_subjects.all()
post_save.connect(sub_related_changed, sender=Knowledge)
I have tried using the function as m2m_changed signal as follows:
m2m_changed.connect(model_saved, sender=Knowledge.item_subjects.through)
But this either generates a recursive loop or doesn't fire.
Once you have the subject objects (as you have in your edit), you can add them with
for sub in item_subjects:
subject, created = Subject.objects.get_or_create(noun=sub)
instance.item_subjects.add(subject)
The "item_subjects" attribute is a way of managing the related items. The through relationships are created via the "add" method.
Once you've done this, you can do things like instance.item_subjects.filter(noun='foo') or instance.item_subjects.all().delete() and so on
Documentation Reference: https://docs.djangoproject.com/en/1.11/topics/db/examples/many_to_many/
EDIT
Ahh I didn't realize that this was taking place in the Django Admin. I think you're right that that's the issue. Upon save, the admin calls two methods: The first is model_save() which calls the model's save() method (where I assume this code lives). The second method it calls is "save_related" which first clears out ManyToMany relationships and then saves them based on the submitted form data. In your case, there is no valid form data because you're creating the objeccts on save.
If you put the relevant parts of this code into the save_related() method of the admin, the changes should persist.
I can be more specific about where it should go if you'll post both your < app >/models.py and your < app >/admin.py files.
Reference from another SO question:
Issue with ManyToMany Relationships not updating inmediatly after save

How to specify label_attr for a model in a Flask-Admin ModelView using MongoEngine?

I think I have a pretty common use case and am surprised at how much trouble it's giving me.
I want to use a key-value pair for a ReferenceField in the Flask-Admin edit form generated by the following two classes:
class Communique(db.Document):
users = db.ListField(db.ReferenceField(User), default=[])
class User(db.Document):
email = db.StringField(max_length=255, required=True)
def __unicode__(self):
return '%s' % self.id
I want the select to be constructed out of the ObjectId and the an email field in my model.
By mapping the __unicode__
attribute to the id field I get nice things on the mongoengine side like using the entire object in queries:
UserInformation.objects(user=current_user)
This has the unfortunate effect of causing the Flask-Admin form to display the mongo ObjectId in the edit form like so:
The docs say I have to provide the label_attr to the ModelSelectMultipleField created by Flask-Admin. I've done so by overriding the get_form method on my ModelView:
def get_form(self):
form = super(ModelView, self).get_form()
form.users = ModelSelectMultipleField(model=User,
label_attr='email',
widget=form.users.__dict__['kwargs']['widget'])
return form
I'm reusing the the widget used by the original form.users (which may be wrong). It works fine when editing an existing item, BUT throws an exception when creating a new one (perhaps because I'm reusing the widget).
All of this seems like way more work than should be needed to simply provide a label_attr to my SelectField. Fixing up the listing view was a simple matter of adding an entry to the column_formatters dictionary. Is there no simple way to specify the label_attr when creating my ModelView class?
I know I could make this problem go away by returning the email property in the __unicode__ attribute, but I feel like I shouldn't have to do that! Am I missing something?
Oy, now I see how to do it, though it's not that obvious from the docs. form_args is a dictionary with items keyed to the form models. All I needed to do was...
form_args = dict(users=dict(label_attr='email'))
Which does seem about the right amount of effort (considering Flask-Admin isn't some sort of java framework).

Gathering information from few forms in django

I am creating simple search engine, so I have one input text field on the top of the page and buttom "search" next to it. That is all in one form, and "produce" for instance/q=search%20query.
In sidebar I have panel with another form with filters, lets say from, to. I want to have a possibility of creating link like /q=search%20query&from=20&to=50. I wonder how button from first form should gather information from second form.
I read somewhere that there is something like formsets, however I didn't find information that they can be used to something like that.
I think this would be most easily solved by making a form which includes both the search term and any filters; you can place the form elements wherever you want.
Your form would have to be something like:
from django import forms
class MySearchForm(forms.Form):
FILTERS = (("lt20", "Less than 20"),
("20to50", "Twenty to fifty"),
)
term = forms.CharField()
filters = forms.MultipleChoiceField(widget = CheckBoxSelectMultiple, choices = FILTERS)
Then you would have to display the form manually, so you can split it up however you want in the template. In other words, instead of using a filter like {{form.as_p}}, you would have to do {{form.term}} and {{form.filters}}.
You could also define your filters in a model, so that you can more easily change them, add to them, etc, st:
# in models.py
class = Filters(models.Model):
name = models.CharField(max_length=20)
# in your form definition,
from your_app_name.models import Filters
class MySearchForm(forms.Form):
term = forms.CharField()
filters = forms.ModelChoiceField(queryset = Filters.objects.all(), widget = CheckBoxSelectMultiple)
I just come up with an idea, that I can create in first form additional hidden fields, which can be synchronized with fields from second form by JavaScript. This will create small redundancy, however seems to be very easy to implement.
Is it a good idea?

Clearing Django form fields on form validation error?

I have a Django form that allows a user to change their password. I find it confusing on form error for the fields to have the *'ed out data still in them.
I've tried several methods for removing form.data, but I keep getting a This QueryDict instance is immutable exception message.
Is there a proper way to clear individual form fields or the entire form data set from clean()?
Regarding the immutable QueryDict error, your problem is almost certainly that you have created your form instance like this:
form = MyForm(request.POST)
This means that form.data is the actual QueryDict created from the POST vars. Since the request itself is immutable, you get an error when you try to change anything in it. In this case, saying
form.data['field'] = None
is exactly the same thing as
request.POST['field'] = None
To get yourself a form that you can modify, you want to construct it like this:
form = MyForm(request.POST.copy())
Someone showed me how to do this. This method is working for me:
post_vars = {}
post_vars.update(request.POST)
form = MyForm(post_vars, auto_id='my-form-%s')
form.data['fieldname'] = ''
form.data['fieldname2'] = ''
If you need extra validation using more than one field from a form, override the .clean() method. But if it's just for one field, you can create a clean_field_name() method.
http://docs.djangoproject.com/en/1.1/ref/forms/validation/#ref-forms-validation
I just created the form again.
Just try:
form = AwesomeForm()
and then render it.
Django has a widget that does that:
https://docs.djangoproject.com/en/1.8/ref/forms/widgets/#passwordinput
now if you are not working with passwords you can do something like this:
class NonceInput(Input):
"""
Hidden Input that resets its value after invalid data is entered
"""
input_type = 'hidden'
is_hidden = True
def render(self, name, value, attrs=None):
return super(NonceInput, self).render(name, None, attrs)
Of course you can make any django widget forget its value just by overriding its render method (instead of value I passed None in super call.)
Can't you just delete the password data from the form's cleaned_data during validation?
See the Django docs for custom validation (especially the second block of code).
I guess you need to use JavaScript to hide or remove the text from the container.

Categories

Resources