How can I add extra textarea fields to a form - python

I need to generate several textareas to be filled by users and submitted back to the model. However, I think they need all to have different names (am i correct?)
How can I generate random names for each textarea and how can the model get the data within one it gets the POST request?
Thanks!
EDIT: I was hoping to use the randomly generated name as a way to identify the content and to save them in the database

It's hard to give you a good answer here because you haven't indicated what you're actually trying to achieve. You could add a 1000 text fields to your form, but if they don't correlate somehow to data on your model, there's not much point, and you've neglected that crucial piece of information.
Still, on a very basic level, you can add the additional textareas to your form like so:
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
for i in range(1, 11):
self.fields['mytextarea%d' % i] = forms.CharField(label='Textarea %d' % i, widget=forms.Textarea)
UPDATE
Based on your comment, and the fact that you're intending to actually store and retrieve the textarea fields at a later point, you should create a separate model for them and link it to your main model through a one-to-many relationship. For example:
class PrimaryModel(models.Model):
... # Stuff here
class TextareaModel(models.Model):
myprimarymodel = models.ForeignKey(PrimaryModel)
mytextarea = models.TextField()
It's hard to give a good example since you haven't indicated anything about what your models look like, but the basic idea is that you have a model that contains nothing but a foreign key to the main model and a textarea field. You can then treat these textareas as inlines with via a model formset.

Related

Django - dynamic form creation from random db entries

This is an oversimplified example of what am I trying to achieve. Let's say I have these two models:
class Question(models.Model):
text = models.CharField(max_length=255)
class Answer(models.Model):
text = models.CharField(max_length=255)
question = models.ForeignKey(Question)
And let's say I have thousands of questions in the database, but I want each user to answer only a few random ones. So, my idea was to create a dynamic form. Something like this:
class QuestionnaireForm(forms.Form):
def __init__(self, *args, **kwargs):
super(QuestionnaireForm, self).__init__(*args, **kwargs)
questions = list(Question.objects.all())
random.shuffle(questions) # it successfully validates without this line
questions = questions[:3]
for q in questions:
self.fields[q.text] = forms.CharField()
When I do this, I get my three random questions as desired, but the form won't validate. If I comment out shuffling, everything works fine, but then I get the same questions every time, obviously.
From what I can see, it seems like Django is calling the __init__ method again on form submission and thus repeating the shuffling and getting different questions. I tried reading through the documentation, but I'm not managing to wrap my head around why is this the way it is.
Random ordering:
questions = Question.objects.all().order_by('?')[:3]
To validate form, it hink, you need restore same queryset with something like:
questions = Question.objects.filter(pk__in=request.POST.getlist('ids'))
and put it to form. Think you need also save same ordering — then you can sort out this list in form.
Update:
You should save state between requests some way. You can add hidden field, add URL parameters, set cookie, save info in user's profile — is up to your choice. Tipical way is set hidden inputs (generally it's default django ModelForm behavior).
So on first request, when you show form — you get queryset, sort it by pk for example, put to form and add hidden fields with IDs. When user made POST request with answer — you'll restore your queryset with this IDs, sort it again same way and put to form to validate.

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?

Django Form with extra information

I'm have a model that has some fields that should never be edited, such as "date created." I want to display a form that has some text fields with the extra information, probably just the data itself in a <span>. A person can edit a few fields and at the same time they can see the extra information about the instance of the model, such as created, last modified, or even related objects in the database for this object. Is there a way to do this using Django's built in framework, or will I have to create my own custom form by passing the whole object to a template?
If this needs more clarification, feel free to ask.
The best way I know to do this is to initialize the fields before you pass the form to the template by passing an initial dictionary to the form or by passing a instance object to the form.
You should then make sure that the fields are disabled, or you should make them hidden fields and then display the fields as regular text.
Most importantly, if you're passing data to the client that will then be sent back in a form, you should make sure that the data coming in is the same as the data that went out (for security's sake). Do this with at clean_[field] function on the Form. It should look like the following.
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
def clean_date_created(self):
if self.cleaned_fields['date_created'] != self.instance.date_created:
raise ValidationError, 'date_created has been tampered'
self.cleaned_fields['date_created']
[Edit/Addendum] Alternatively, you can pass the data directly to your template to render separately, and then tack on the data to your form after you get it back into your view. It should go something like this:
def recieve_form(request, ...):
...
f = MyForm(request.POST, instance=a)
new_model_instance = f.save(commit=False)
new_model_instance.date_created = <whatever>
new_model_instance.save()
To do that I usually customize the form in order for the widget to be read only, like the following:
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
widgets = {
'my_field': forms.TextInput(attrs={'disabled':'disabled'}),
}
This will output a read only text field but I guess you can also create a Widget that will just output a <div> or a <p> (or whatever you need)

How to set for m2m-field different querysets for each inline object?

So I have a model that is shown in inline form.
That model have ManyToManyField.
Imagine that there are several inline-objects that are already created.
The problem is how to show different querysets of available objects in my m2m-field based on original inline-object.
One more time:)
I mean that in each inline-object must by m2m-field with different available variants.
Variants will of course include all that is actually set for this inline-object + they must include only variants that are not present at the moment anywhere else.
Thanks.
Question is very poorly written, so it's hard to be sure exactly what you're looking for, but my best guess is that you're want to limit the queryset for the ManyToManyField to items that are not assigned to anything else? If that's correct:
(You also didn't post an example model, so I'll make one up to illustrate)
class SomeModel(models.Model):
my_m2m_field = models.ManyToManyField(OtherModel)
And, here's the code to limit the field based on that:
class SomeModelInlineAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyInlineAdminForm, self).__init__(*args, **kwargs)
self.fields['my_m2m_field'].queryset = OtherModel.objects.filter(somemodel__isnull=True)
class SomeModelInlineAdmin(admin.TabularInline):
model = SomeModel
form = SomeModelInlineAdminForm

Ordered ManyToManyField that can be used in fieldsets

I've been working through an ordered ManyToManyField widget, and have the front-end aspect of it working nicely:
Unfortunately, I'm having a great deal of trouble getting the backend working. The obvious way to hook up the backend is to use a through table keyed off a model with ForeignKeys to both sides of the relationship and overwrite the save method. This would work great, except that due to idiosyncrasies of the content, it is an absolute requirement that this widget be placed in a fieldset (using the ModelAdmin fieldsets property), which is apparently not possible.
I'm out of ideas. Any suggestions?
Thanks!
In regard to how to set up the models, you're right in that a through table with an "order" column is the ideal way to represent it. You're also right in that Django will not let you refer to that relationship in a fieldset. The trick to cracking this problem is to remember that the field names you specify in the "fieldsets" or "fields" of a ModelAdmin do not actually refer to the fields of the Model, but to the fields of the ModelForm, which we are free to override to our heart's delight. With many2many fields, this gets tricky, but bear with me:
Let's say you're trying to represent contests and competitors that compete in them, with an ordered many2many between contests and competitors where the order represents the competitors' ranking in that contest. Your models.py would then look like this:
from django.db import models
class Contest(models.Model):
name = models.CharField(max_length=50)
# More fields here, if you like.
contestants = models.ManyToManyField('Contestant', through='ContestResults')
class Contestant(models.Model):
name = models.CharField(max_length=50)
class ContestResults(models.Model):
contest = models.ForeignKey(Contest)
contestant = models.ForeignKey(Contestant)
rank = models.IntegerField()
Hopefully, this is similar to what you're dealing with. Now, for the admin. I've written an example admin.py with plenty of comments to explain what's happening, but here's a summary to help you along:
Since I don't have the code to the ordered m2m widget you've written, I've used a placeholder dummy widget that simply inherits from TextInput. The input holds a comma-separated list (without spaces) of contestant IDs, and the order of their appearance in the string determines the value of their "rank" column in the ContestResults model.
What happens is that we override the default ModelForm for Contest with our own, and then define a "results" field inside it (we can't call the field "contestants", since there would be a name conflict with the m2m field in the model). We then override __init__(), which is called when the form is displayed in the admin, so we can fetch any ContestResults that may have already been defined for the Contest, and use them to populate the widget. We also override save(), so that we can in turn get the data from the widget and create the needed ContestResults.
Note that for the sake of simplicity this example omits things like validation of the data from the widget, so things will break if you try to type in anything unexpected in the text input. Also, the code for creating the ContestResults is quite simplistic, and could be greatly improved upon.
I should also add that I've actually ran this code and verified that it works.
from django import forms
from django.contrib import admin
from models import Contest, Contestant, ContestResults
# Generates a function that sequentially calls the two functions that were
# passed to it
def func_concat(old_func, new_func):
def function():
old_func()
new_func()
return function
# A dummy widget to be replaced with your own.
class OrderedManyToManyWidget(forms.widgets.TextInput):
pass
# A simple CharField that shows a comma-separated list of contestant IDs.
class ResultsField(forms.CharField):
widget = OrderedManyToManyWidget()
class ContestAdminForm(forms.models.ModelForm):
# Any fields declared here can be referred to in the "fieldsets" or
# "fields" of the ModelAdmin. It is crucial that our custom field does not
# use the same name as the m2m field field in the model ("contestants" in
# our example).
results = ResultsField()
# Be sure to specify your model here.
class Meta:
model = Contest
# Override init so we can populate the form field with the existing data.
def __init__(self, *args, **kwargs):
instance = kwargs.get('instance', None)
# See if we are editing an existing Contest. If not, there is nothing
# to be done.
if instance and instance.pk:
# Get a list of all the IDs of the contestants already specified
# for this contest.
contestants = ContestResults.objects.filter(contest=instance).order_by('rank').values_list('contestant_id', flat=True)
# Make them into a comma-separated string, and put them in our
# custom field.
self.base_fields['results'].initial = ','.join(map(str, contestants))
# Depending on how you've written your widget, you can pass things
# like a list of available contestants to it here, if necessary.
super(ContestAdminForm, self).__init__(*args, **kwargs)
def save(self, *args, **kwargs):
# This "commit" business complicates things somewhat. When true, it
# means that the model instance will actually be saved and all is
# good. When false, save() returns an unsaved instance of the model.
# When save() calls are made by the Django admin, commit is pretty
# much invariably false, though I'm not sure why. This is a problem
# because when creating a new Contest instance, it needs to have been
# saved in the DB and have a PK, before we can create ContestResults.
# Fortunately, all models have a built-in method called save_m2m()
# which will always be executed after save(), and we can append our
# ContestResults-creating code to the existing same_m2m() method.
commit = kwargs.get('commit', True)
# Save the Contest and get an instance of the saved model
instance = super(ContestAdminForm, self).save(*args, **kwargs)
# This is known as a lexical closure, which means that if we store
# this function and execute it later on, it will execute in the same
# context (i.e. it will have access to the current instance and self).
def save_m2m():
# This is really naive code and should be improved upon,
# especially in terms of validation, but the basic gist is to make
# the needed ContestResults. For now, we'll just delete any
# existing ContestResults for this Contest and create them anew.
ContestResults.objects.filter(contest=instance).delete()
# Make a list of (rank, contestant ID) tuples from the comma-
# -separated list of contestant IDs we get from the results field.
formdata = enumerate(map(int, self.cleaned_data['results'].split(',')), 1)
for rank, contestant in formdata:
ContestResults.objects.create(contest=instance, contestant_id=contestant, rank=rank)
if commit:
# If we're committing (fat chance), simply run the closure.
save_m2m()
else:
# Using a function concatenator, ensure our save_m2m closure is
# called after the existing save_m2m function (which will be
# called later on if commit is False).
self.save_m2m = func_concat(self.save_m2m, save_m2m)
# Return the instance like a good save() method.
return instance
class ContestAdmin(admin.ModelAdmin):
# The precious fieldsets.
fieldsets = (
('Basic Info', {
'fields': ('name', 'results',)
}),)
# Here's where we override our form
form = ContestAdminForm
admin.site.register(Contest, ContestAdmin)
In case you're wondering, I had ran into this problem myself on a project I've been working on, so most of this code comes from that project. I hope you find it useful.

Categories

Resources