Pre-selecting multiple checkboxes in Django forms - django

Very similar to this question, but I tried the accepted answer and it did not work. Here's what's going on.
I have a form for tagging people in photos that looks like this:
forms.py
class TaggingForm(forms.Form):
def __init__(self, *args, **kwargs):
queryset = kwargs.pop('queryset')
super(TaggingForm, self).__init__(*args, **kwargs)
self.fields['people'] = forms.ModelMultipleChoiceField(required=False, queryset=queryset, widget=forms.CheckboxSelectMultiple)
...
models.py
class Photo(models.Model):
user = models.ForeignKey(User)
...
class Person(models.Model):
user = models.ForeignKey(User)
photos = models.ManyToManyField(Photo)
...
I want users to be able to edit the tags on their photos after they initially tag them, so I have a page where they can go to view a single photo and edit its tags. For obvious reasons I want to have the already-tagged individuals' checkboxes pre-selected. I tried to do this by giving the form's initial dictionary a list of people I wanted selected, as in the answer to the question I linked above.
views.py
def photo_detail(request,photo_id):
photo = Photo.objects.get(id=photo_id)
initial = {'photo_id':photo.id, 'people':[p for p in photo.person_set.all()]}
form_queryset = Person.objects.filter(user=request.user)
if request.method == "POST":
form = TaggingForm(request.POST, queryset=form_queryset)
# do stuff
else:
form = TaggingForm(initial=initial, queryset=form_queryset)
...
When I try to initialize people as in the above code, the form doesn't show up, but no errors are thrown either. If I take the 'people' key/value pair out of the initial dictionary the form shows up fine, but without any people checked.
Also I'm using Django 1.5 if that matters. Thanks in advance.

What you could do is simply use django forms to handle all of this for you. Please refer to this question. Ideally it boils down to lettings djnago handle your forms and its validation and initial values.
Now this is actually a really good practice to get used to since, you're dissecting all your logic and your presentation. Its a great DRY principle.

Related

Setting fields in Django form, when using ModelForm

My model is that I have a list of jobs, and each job can only be done by a subset of users (ManyToManyField). People can then submit job requests, and assign the job to someone from the subset of people who can do the job:
class Job(models.Model):
...
users = models.ManyToManyField(User)
class Job_Request(models.Model):
...
job = models.ForeignKey(Job)
assigned_to = models.ForeignKey(User)
I then created a form using ModelForm, to allow people to edit the job request, to reassign the job to someone else. My problem, is that ModelForm creates a menu for the "assigned_to" field in the form, which lists all of our users. I only want it to show the subset of users that can do that job. How can I do this?
Below is my forms.py, where I tried setting the assigned_to field to the subset of users that can do the job, but I don't know the correct syntax. The following is definitely wrong, as it creates an empty menu. How can I do this, either in the form, or the template? Thanks.
class EditJobRequestForm(ModelForm):
def __init__(self, *args, **kwargs):
super(EditJobRequestForm, self).__init__(*args, **kwargs)
self.fields['assigned_to'].queryset =
User.objects.filter(username__in=self.instance.job.users.all()]
self.fields['assigned_to'].queryset = self.instance.job.users.all()
Remember that job must be set in instance before send model to form
User.objects.filter(username__in=self.instance.job.users.all()]
You're querying against usernames vs PKs. Of course there is no match.
User.objects.filter(pk__in=self.instance.job.users.all()]

How to pass in a parameter to the form within the formset?

I would like to create a formset, where each form has a dropdown pointing to a set of sales items.
Model:
class SalesItem(models.Model):
item_description = models.CharField(max_length=40)
company = models.ForeignKey(Company)
Here I create a form with a dropdown, hoping to pass in the company as a source for the dropdown. Hold on to this thought, as I think that is not possible in my scenario.
Form:
class SalesItemFSForm(Form):
sales_item = forms.ModelChoiceField(required=False, queryset = '')
def __init__(self, company, *args, **kwargs):
super(SalesItemFSForm, self).__init__(*args, **kwargs)
self.fields.sales_item.queryset = company.salesitem_set.all()
Now within my view I would like to create a formset with this form:
formset_type = formset_factory(SalesItemFSForm, extra=0)
The problem becomes right away clear, as there seem to be no way that I could pass in the company to determine the source for the dropdown.
How am I supposed to do this?
Many Thanks,
Update:
it seems Jingo cracked it. :)
A ModelForm works better than a Form. On top of it I had to add fields = {} to SalesItemFSForm, to make sure that the SalesItem's fields are not showing up in the template. Because all we are interested in is our dropdown (SalesItem).
So far so good. But now I see as many dropdowns shown as I have Salesitems. It shouldn;t show any unless the user presses a jquery button.
And I think this is the problem, we should NOT pass in
formset_type = modelformset_factory(SalesItem, form=SalesItemFSForm, extra=0)
Because our form doesn't need any instance of the SalesItem. We need a dummy Model.
That was the reason I tried to solve it initially with classic Formset instead of ModelFormset. So its kind of half way there. :)
Update 2:
Jingo, good point. Effectively I was thinking of a custom save, where I just see how many formsets are added by the user via jQuery and save it myself within the view. Literally SalesItem is a ManyToMany field. But the standard M2m widget is horrible. Hence I wanted to replace it with formsets, where each salesItem is a dropdown. The user can then add as many dropdowns (forms in formset) to the page and submit them. Then I would add the relationship in the view.
class DealType(models.Model):
deal_name = models.CharField(_(u"Deal Name"), max_length=40)
sales_item = models.ManyToManyField(SalesItem)
price = models.DecimalField(decimal_places=2, max_digits=12)
Hope this makes it clear. Maybe there is an easier way to do this. :)
Btw I also found this excellent jquery snippet code how to add/remove forms to/from a formset.
Update 3:
Indeed when instantiating the object like this, we would only get one form in the formset and can add more via jquery. Perfect!! Unless there is an easier way to achieve this. :)
salesitem_formsets = formset_type(queryset=SalesItem.objects.filter(pk=1))
However this comes back hunting you in the request.POST, since you can't just do:
salesitem_formsets = formset_type(request.POST)
It still requires the queryset to be set. Tricky situation...
I hope I understood the goal you want to achieve right. Then maybe you could use ModelForm and its available instance like this:
class SalesItemFSForm(forms.ModelForm):
class Meta:
model = SalesItem
def __init__(self, *args, **kwargs):
super(SalesItemFSForm, self).__init__(*args, **kwargs)
self.sale_items = self.instance.company.salesitem_set.all()
self.fields['sales_item'] = forms.ModelChoiceField(queryset=self.sale_items)
This is untested though and just a thought. I hope this leads into the right direction, but if its totally wrong, let me know and i will remove my answer, so that others wont be confused :).

django: use a queryset as modelform initial data

I'm making a settings interface which works by scanning for a settings folder in the installed applications, scanning for settings files, and finally scanning for ModelForms.
I'm at the last step now. The forms are properly found and loaded, but I now need to provide the initial data. The initial data is to be pulled from the database, and, as you can imagine, it must be limited to the authenticated user (via request.user.id).
Keep in mind, this is all done dynamically. None of the names for anything, nor their structure is known in advanced (I really don't want to maintain a boring settings interface).
Here is an example settings form. I just pick the model and which fields the user can edit (this is the extent to which I want to maintain a settings interface).
class Set_Personal_Info(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('nick_name', 'url')
I've looked at modelformset_factory which almost does what I want to do, but it only seems to work with results of two or more. (Here, obj is one of the settings forms)
Formset = modelformset_factory(obj.Meta.model, form=obj)
Formset(queryset=obj.Meta.model.objects.filter(id=request.user.id))
I can't filter the data, I have to get one, and only one result. Unfortunately I can't use get()
Formset = modelformset_factory(obj.Meta.model, form=obj)
Formset(queryset=obj.Meta.model.objects.get(id=request.user.id))
'User' object has no attribute 'ordered'
Providing the query result as initial data also doesn't work as it's not a list.
Formset = modelformset_factory(obj.Meta.model, form=obj)
Formset(initial=obj.Meta.model.objects.get(id=request.user.id))
'User' object does not support indexing
I have a feeling that the answer is right in front of me. How can I pull database from the database and shove it into the form as initial values?
I'm not really sure I understand what you're trying to do - if you're just interested in a single form, I don't know why you're getting involved in formsets at all.
To populate a modelform with initial data from the database, you just pass the instance argument:
my_form = Set_Personal_Info(instance=UserProfile.objects.get(id=request.user.id))
Don't forget to also pass the instance argument when you're instantiating the form on POST, so that Django updates the existing instance rather than creating a new one.
(Note you might want to think about giving better names to your objects. obj usually describes a model instance, rather than a form, for which form would be a better name. And form classes should follow PEP8, and probably include the word 'form' - so PersonalInfoForm would be a good name.)
Based on what I've understand ... if you want to generate a form with dynamic fields you can use this:
class MyModelForm(forms.ModelForm):
def __init__(self, dynamic_fields, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.fields = fields_for_model(self._meta.model, dynamic_fields, self._meta.exclude, self._meta.widgets)
class Meta:
model = MyModel
Where dynamic_fields is a tuple.
More on dynamic forms:
http://www.rossp.org/blog/2008/dec/15/modelforms/
http://jacobian.org/writing/dynamic-form-generation/
http://dougalmatthews.com/articles/2009/dec/16/nicer-dynamic-forms-django/
Also Daniel's approach is valid and clean ... Based on your different ids/types etc you can you use different Form objects
forms.py
class MyModelFormA(forms.ModelForm):
class Meta:
model = MyModel
fields = ('field_a','field_b','field_c')
class MyModelFormB(forms.ModelForm):
class Meta:
model = MyModel
fields = ('field_d','field_e','field_f')
views.py
if request.method == 'POST':
if id == 1:
form = MyModelFormA(data=request.POST)
elif id == 2:
form = MyModelFormB(data=request.POST)
else:
form = MyModelFormN(data=request.POST)
if form.is_valid():
form.save() else:
if id == 1:
form = MyModelFormA()
elif id == 2:
form = MyModelFormB()
else:
form = MyModelFormN()

django.contrib.comments and multiple comment forms

I followed the guide at https://docs.djangoproject.com/en/dev/ref/contrib/comments/custom/ to set up a comment form on News entries for my current django app. Now, I need to have a comment form with different fields for another type of Object in another part of the site.
How should this be accomplised considering I've overridden the contact form already?
That's a good question; django does seem pretty insistent that you use the same comment form everywhere. You can probably write a single form that shows different fields based on the object it's instantiated with. Try writing an init method along the lines of this:
class CustomCommentForm(CommentForm):
custom_field = forms.CharField(max_length=100)
def __init__(self, *args, **kwargs):
super(CustomCommentForm, self).__init__(*args, **kwargs)
# check what's in kwargs['initial'], and insert fields if needed like this:
if ...:
self.fields['optional_field'] = forms.CharField(max_length=100)

Django: How to save a formset with a custom model form?

Trying to save a bunch of objects but with a custom form:
class CustomForm(forms.ModelForm):
class Meta:
model = Widget
complexify = models.BooleanField()
When complexify is checked, i need to do some complex operations on the widget object.
I can't do:
for object in formset.save(commit=False):
...
because it won't have the complexify flag.
And going through each form seems to be the wrong way:
for form in formset.forms:
...
because it includes the extra (empty) forms and the deleted forms.
Any ideas on how to get this done?
The best answer i could find to this problem was overriding the save on the form:
class CustomForm(forms.ModelForm):
class Meta:
model = Widget
complexify = models.BooleanField()
def save(self, *args, **kwargs):
obj = super(CustomForm, self).save(*args, **kwargs)
obj.complexify = self.cleaned_data.get("complexify")
return obj
then it will be available for you when you handle them:
for object in formset.save(commit=False):
if object.complexify:
object.do_complicated()
I ran into a similar problem, needing to update a field in my forms before saving them. My solution was to do something like what you suggested above, but then skip over forms that hadn't been changed by using the method has_changed, like so:
for form in formset.forms:
object = form.save(commit=False)
if form.has_changed():
#make additions to object here
object.save()
I've never worked with the complexify flag, but your question seemed to run along the lines of my own problem, so I thought I'd pass the info along. Of course, if anyone sees anything that will lead to problems later with this approach, please let me know, I'm still a Django beginner.