Django MultipleChoiceField choices of users throws error - python

I am trying to write a form that allows the user to select as many users from a specific group as they want. However when I try to use the list of users as an option I get an error saying that 'User' object does not support indexing.
Its a fairly standard form, the main difference is that the group is filtered based on a kwarg passed to the form. The form is passed a project_id (project object primary key) and it then finds the group associated with that project and generates the field.
From forms.py
class ModifyTeamForm(forms.Form):
action = ChoiceField(choices=[('remove', 'Remove users'), ('promote', 'Promote to lead.')])
def __init__(self, *args, **kwargs):
# The project to get the team for
project_id = kwargs.pop('project_id', None)
super(ModifyTeamForm, self).__init__(*args, **kwargs)
project = Project.objects.get(pk=project_id)
# Team for this project
team = User.objects.filter(groups__name=project.project_name)
# Create a form field to select current team members
current_team = MultipleChoiceField(required=True, choices = team, widget=CheckboxSelectMultiple)
# Add the field
self.fields['current_team'] = current_team
My views.py
#login_required
def team(request, project_id):
if request.method == "POST":
# Not yet implemented
return
else:
form = ModifyTeamForm(project_id=project_id)
template = loader.get_template('projects/team.html')
context = RequestContext(request, {
'form': form,
})
return HttpResponse(template.render(context))

It's because MultipleChoiceField.choices is expected to be a 2d Array effectively (https://docs.djangoproject.com/en/1.7/ref/forms/fields/#django.forms.ChoiceField.choices).
So you could do something like this:
team = [(u.pk, u.email) for u in User.objects.filter(groups__name=project.project_name)]
And that will return you a list continaing the combintation of
[('user1.pk', 'user1.email'), ('user2.pk', 'user2.email'),...]
which will be useable as the choices.

Related

Django - Questionnaire - ModelFormSet - Cannot assign

Alright, so I'm following a tutorial on creating a small questionnaire with Django.
A User can create a survey with different multiple choice questions. Since they're multiple choice questions, users can also set the options for the questions.
A "survey taker" can then start a survey, select his or her preferred questions and submit the form.
I would like to only show a single question per page so I'm trying to work with modelformset_factory and implement pagination using the build in paginator.
The form is rendered correctly and a user can submit an answer, however, the form fails before "formset.is_valid()", I just can't figure out why;
ValueError at /surveys/1/submit/4/
Cannot assign "'2'": "Answer.option" must be a "Option" instance.
So I can not save an integer and I somehow have to relate the integer with the id from the option model... But I can't access cleaned_data yet so I guess I'm missing something here. Did I forget something within the formset?
I've been staring at this for a while now so any help is appreciated.
#Views
def submit(request, survey_pk, sub_pk):
# Let's retrieve the survey which is created by the survey-taker
try:
survey = Survey.objects.prefetch_related("question_set__option_set").get(
pk=survey_pk, is_active=True
)
except Survey.DoesNotExist:
raise Http404()
try:
submission = survey.submission_set.get(pk=sub_pk, is_complete=False)
except Submission.DoesNotExist:
raise Http404()
# Retrieve all question associated with this survey
questions = survey.question_set.all()
# Get all related options from the questions
options = [q.option_set.all() for q in questions]
form_kwargs = {"empty_permitted": False, "options": options,}
# Setup a formset utilizing a ModelFormSet
AnswerFormset = modelformset_factory(
Answer,
form=AnswerModelForm,
formset=BaseAnswerFormSet,
exclude=(),
extra=len(questions),
)
if request.method == "POST":
print("Request.POST", request.POST)
# This is where the problem starts
formset = AnswerFormset(request.POST, form_kwargs=form_kwargs)
print("Formset: ", formset)
if formset.is_valid():
print("Formset is valid")
with transaction.atomic():
for form in formset:
Answer.objects.create(
option_id=form.cleaned_data["option"],
submission_id=sub_pk,
)
submission.is_complete = True
submission.save
return redirect("survey-thanks", pk=survey_pk)
else:
formset = AnswerFormset(form_kwargs=form_kwargs)
print(form_kwargs)
question_forms = zip(questions, formset)
return render(
request,
"survey/submit.html",
{
"survey": survey,
"question_forms": question_forms,
"formset": formset
}
)
#forms
# Setup a Modelformset
class AnswerModelForm(forms.ModelForm):
class Meta:
model = Answer
fields = "option",
def __init__(self, *args, **kwargs):
options = kwargs.pop("options")
# Options must be a list of Option objects
choices = {(o.pk, o.text) for o in options}
super().__init__(*args, **kwargs)
option_field = forms.ChoiceField(choices=choices, widget=forms.RadioSelect, required=True)
self.fields["option"] = option_field
class BaseAnswerFormSet(forms.BaseFormSet):
def get_form_kwargs(self, index):
kwargs = super().get_form_kwargs(index)
kwargs["options"] = kwargs["options"][index]
return kwargs
#models
class Answer(models.Model):
"""An answer a survey's questions."""
submission = models.ForeignKey(Submission, on_delete=models.CASCADE)
option = models.ForeignKey(Option, on_delete=models.CASCADE)
option_id=form.cleaned_data["option"],
is the suspect, it may require an option instance, even though you are trying to pass the id. Try changing option_id to option__id and explicitly setting the form.cleaned_data["option"] to int. Another way to to is to pull the option manually and pass it directly:
option = Option.object.get(id=int(form.cleaned_data["option"]))

Django: Pass a variable/parameter to form from view? [duplicate]

I have a Model as follows:
class TankJournal(models.Model):
user = models.ForeignKey(User)
tank = models.ForeignKey(TankProfile)
ts = models.IntegerField(max_length=15)
title = models.CharField(max_length=50)
body = models.TextField()
I also have a model form for the above model as follows:
class JournalForm(ModelForm):
tank = forms.IntegerField(widget=forms.HiddenInput())
class Meta:
model = TankJournal
exclude = ('user','ts')
I want to know how to set the default value for that tank hidden field. Here is my function to show/save the form so far:
def addJournal(request, id=0):
if not request.user.is_authenticated():
return HttpResponseRedirect('/')
# checking if they own the tank
from django.contrib.auth.models import User
user = User.objects.get(pk=request.session['id'])
if request.method == 'POST':
form = JournalForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
# setting the user and ts
from time import time
obj.ts = int(time())
obj.user = user
obj.tank = TankProfile.objects.get(pk=form.cleaned_data['tank_id'])
# saving the test
obj.save()
else:
form = JournalForm()
try:
tank = TankProfile.objects.get(user=user, id=id)
except TankProfile.DoesNotExist:
return HttpResponseRedirect('/error/')
You can use Form.initial, which is explained here.
You have two options either populate the value when calling form constructor:
form = JournalForm(initial={'tank': 123})
or set the value in the form definition:
tank = forms.IntegerField(widget=forms.HiddenInput(), initial=123)
Other solution: Set initial after creating the form:
form.fields['tank'].initial = 123
If you are creating modelform from POST values initial can be assigned this way:
form = SomeModelForm(request.POST, initial={"option": "10"})
https://docs.djangoproject.com/en/1.10/topics/forms/modelforms/#providing-initial-values
I had this other solution (I'm posting it in case someone else as me is using the following method from the model):
class onlyUserIsActiveField(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(onlyUserIsActiveField, self).__init__(*args, **kwargs)
self.fields['is_active'].initial = False
class Meta:
model = User
fields = ['is_active']
labels = {'is_active': 'Is Active'}
widgets = {
'is_active': forms.CheckboxInput( attrs={
'class': 'form-control bootstrap-switch',
'data-size': 'mini',
'data-on-color': 'success',
'data-on-text': 'Active',
'data-off-color': 'danger',
'data-off-text': 'Inactive',
'name': 'is_active',
})
}
The initial is definded on the __init__ function as self.fields['is_active'].initial = False
As explained in Django docs, initial is not default.
The initial value of a field is intended to be displayed in an HTML . But if the user delete this value, and finally send back a blank value for this field, the initial value is lost. So you do not obtain what is expected by a default behaviour.
The default behaviour is : the value that validation process will take if data argument do not contain any value for the field.
To implement that, a straightforward way is to combine initial and clean_<field>():
class JournalForm(ModelForm):
tank = forms.IntegerField(widget=forms.HiddenInput(), initial=123)
(...)
def clean_tank(self):
if not self['tank'].html_name in self.data:
return self.fields['tank'].initial
return self.cleaned_data['tank']
If you want to add initial value and post other value you have to add the following :
or None after request.POST
form = JournalForm(request.POST or None,initial={'tank': 123})
If you want to add files or images also
form = JournalForm(request.POST or None,request.FILES or None,initial={'tank': 123})
I hope this can help you:
form.instance.updatedby = form.cleaned_data['updatedby'] = request.user.id
I also encountered the need to set default values in the form during development. My solution is
initial={"":""}
form=ArticleModel(request.POST)
if form.has_changed():
data = {i: form.cleaned_data[i] for i in form.changed_data}
data.update({key: val for key, val in init_praram.items() if key not in form.changed_data})
use form.has_changed ,if form.fields is required you can use this method
How I added the initial to the form:
I read #Sergey Golovchenko answer.
So I just added it to the form in if request.method == 'POST':.
But that's not where you place it, if you want to see what value it got before posting the form.
You need to put it in the form where the else is.
Example here from views.py
def myForm(request):
kontext = {}
if request.method == 'POST':
# You might want to use clean_data instead of initial here. I found something on a stack overflow question, and you add clean data to the Forms.py, if you want to change the post data. https://stackoverflow.com/questions/36711229/django-forms-clean-data
form = myModelForm(request.POST, initial={'user': request.user})
if form.is_valid():
form.save()
return redirect('/')
else:
# you need to put initial here, if you want to see the value before you post it
form = myModelForm(initial={'user': request.user})
kontext['form'] = form
return render(request, 'app1/my_form.html', kontext)

Saving Django Form Data

I am learning Django forms and am trying to save form data. I have a working form, but I can't figure out to 'do' anything with the data entered on the form. Specifically, I am trying to do the following two things:
First, once the user submits the form, load a new page that states: "You searched for 'X'".
Second, have the form data interact with an existing database. Specifically, I have a model called 'Hashtag' that has two attributes: 'search_text' and 'locations'. I think the process would work as follows:
Send X to the Model ('Hashtag'),
If X is equal to an existing hashtag.search_text object in the database, then return a page with: "The following are the locations for 'X': 'Y'
If X doesn't equal an existing hashtag.search_text object in the database, then return a page with: "The following are the locations for 'X': no locations found".
Where,
X = user-inputted form data
Y = hashtag.locations.all() in a list
Thus far, I have the below:
models.py
from django.db import models
class Hashtag(models.Model):
"""
Model representing a specific hashtag search. The model contains two attributes:
1) a search_text (eg 'trump') for which there will be only one for database entry (the row),
2) a list of locations (eg ['LA, CA', 'LA, CA', 'NY, NYC', 'London, UK', 'London, United Kingdom']) for which there may be 0+ per search_text.
"""
search_text = models.CharField(max_length=140, primary_key=True)
locations = models.TextField()
def __str__(self):
""" String for representing the Model object (search_text) """
return self.search_text
def display_locations(self):
""" Creates a list of the locations """
# ISSUE: insert correct code, something like: return '[, ]'.join(hastagsearch.location_list for location in self.location.all())
pass
forms.py
from django import forms
from django.forms import ModelForm
from .models import Hashtag
class SearchHashtagForm(ModelForm):
""" ModelForm for user to search by hashtag """
def clean_hashtag(self):
data = self.cleaned_data['search_text']
# Check search_query doesn't include '#'. If so, remove it.
if data[0] == '#':
data = data[1:]
# return the cleaned data
return data
class Meta:
model = Hashtag
fields = ['search_text',]
labels = {'search_text':('Hashtag Search'), }
help_texts = { 'search_text': ('Enter a hastag to search.'), }
views.py
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from .models import Hashtag
from .forms import SearchHashtagForm
def hashtag_search_index(request):
""" View for index page for user to input search query """
hashtag_search = get_object_or_404(Hashtag)
# If POST, process Form data
if request.method == 'POST':
# Create a form instance and populate it with data from request (binding):
form = SearchHashtagForm(request.POST)
# Check if form is valid
if form.is_valid():
# process the form data in form.cleaned_data as required
hashtag_search.search_text = form.cleaned_data['search_text']
# the reason we can use .save() is because we associated the form with the model as a ModelForm
hashtag_search.save()
# redirect to a new URL
return HttpResponseRedirect(reverse('mapping_twitter:hashtag_search_query'))
# If GET (or any other method), create the default form
else:
form = SearchHashtagForm()
context = {'hashtag_search':hashtag_search, 'form':form}
return render(request, 'mapping_twitter/hashtag_search_query.html', context)
I am considering that a potential way to achieve this is to create another model and save the user-inputted form data there. I am wondering whether that is correct, and how that solution could be used to achieve the Second stated goal above :)
Thanks and apologies in advance if my explanation is a mess/plain wrong :/
EDIT
The EDIT below has made the following changes:
Updated models.py as per #Wiggy A.'s answer,
Updated views.py to include def results()
Included a link to the repo on GitHub.
models.py
from django.db import models
class Location(models.Model):
""" Model representing a Location, attached to Hashtag objects through a
M2M relationship """
name = models.CharField(max_length=140)
def __str__(self):
return self.name
class Hashtag(models.Model):
""" Model representing a specific Hashtag serch, containing two attributes:
1) A `search_text` (fe 'trump'), for which there will be only one per
database entry,
2) A list of `locations` (fe ['LA, CA', 'NY, NYC']), for which there
may be any number of per `search_text` """
search_text = models.CharField(max_length=140, primary_key=True)
locations = models.ManyToManyField(Location, blank=True)
def __str__(self):
""" String for representing the Model object (search_text) """
return self.search_text
def display_locations(self):
""" Creates a list of the locations """
# Return a list of location names attached to the Hashtag model
return self.locations.values_list('name', flat=True).all()
views.py
...
def results(request):
""" View for search results for `locations` associated with user-inputted `search_text` """
search_text = hashtag_search
location_list = Hashtag.display_locations()
context = {'search_text':search_text, 'location_list':location_list}
return render(request, 'mapping_twitter/results.html')
The full repo can be found here: https://github.com/darcyprice/Mapping-Data
EDIT 2
The EDIT below makes the following changes:
Updated views.py to include #Wiggy A.'s suggested amendment of def results()
Included a copy of the ERROR message received due to the updated changes.
Although I copied directly from the Mozilla tutorial (https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Forms), I suspect that the line: hashtag_search.search_text = form.cleaned_data['search_text'] doesn't correctly store hashtag_search.
ERROR
NameError at /search_query/
name 'hashtag_search' is not defined
Request Method: POST
Request URL: http://ozxlitwi.apps.lair.io/search_query/
Django Version: 2.0
Exception Type: NameError
Exception Value:
name 'hashtag_search' is not defined
Exception Location: /mnt/project/mapping_twitter/views.py in hashtag_search_index, line 24
Python Executable: /mnt/data/.python-3.6/bin/python
Python Version: 3.6.5
Python Path:
['/mnt/project',
'/mnt/data/.python-3.6/lib/python36.zip',
'/mnt/data/.python-3.6/lib/python3.6',
'/mnt/data/.python-3.6/lib/python3.6/lib-dynload',
'/usr/local/lib/python3.6',
'/mnt/data/.python-3.6/lib/python3.6/site-packages']
views.py
def hashtag_search_index(request):
""" View for index page for user to input search query """
# If POST, process Form data
if request.method == 'POST':
# Create a form instance and populate it with data from request (binding):
form = SearchHashtagForm(request.POST)
# Check if form is valid
if form.is_valid():
hashtag_search.search_text = form.cleaned_data['search_text']
hashtag_search.save()
# redirect to a new URL
return HttpResponseRedirect(reverse('mapping_twitter:results'))
# If GET (or any other method), create the default form
else:
form = SearchHashtagForm()
context = {'hashtag_search':hashtag_search, 'form':form}
return render(request, 'mapping_twitter/hashtag_search_index.html', context)
def results(request):
""" View for search results for `locations` associated with user-inputted `search_text` """
search_text = hashtag_search
location = get_object_or_404(Hashtag, search_text=search_text)
location_list = location.display_locations()
context = {'search_text':search_text, 'location_list':location_list}
return render(request, 'mapping_twitter/results.html', context)
Turn the locations attribute into a M2M field. That sounds like what you need here. Keep in mind that this is untested code.
models.py
from django.db import models
class Location(models.Model):
""" A model representing a Location, attached to Hashtag objects through a Many2Many relationship """
name = models.CharField(max_length=140)
def __str__(self):
return self.name
class Hashtag(models.Model):
"""
Model representing a specific hashtag search. The model contains two attributes:
1) a search_text (eg 'trump') for which there will be only one for database entry (the row),
2) a list of locations (eg ['LA, CA', 'LA, CA', 'NY, NYC', 'London, UK', 'London, United Kingdom']) for which there may be 0+ per search_text.
"""
search_text = models.CharField(max_length=140, primary_key=True)
locations = models.ManyToManyField(Location)
def __str__(self):
""" String for representing the Model object (search_text) """
return self.search_text
def display_locations(self):
""" Creates a list of the locations """
# This will return a list of location names attached to the Hashtag model
return self.locations.values_list('name', flat=True).all()
views.py
...
def results(request):
""" View for search results for `locations` associated with user-inputted `search_text` """
search_text = hashtag_search
location = get_object_or_404(Hashtag, search_text=search_text)
location_list = location.display_locations()
context = {'search_text':search_text, 'location_list':location_list}
return render(request, 'mapping_twitter/results.html')

Django forms initial value user_full_name

Is the any solution to get django's user_full_name as a initial value for form? My idea was to display a django's form on the end of shopping to finish a order. I want also do put into a form total value, but this is for later.
I did something like this:
user_dane = request.user.get_full_name
koszyk = request.session.get('koszyk', [])
produkty = list(Produkt.objects.filter(pk__in=koszyk))
suma_cen = Produkt.objects.filter(pk__in=koszyk).aggregate(suma=Sum('cena'))
suma_wszystkich_cen = suma_cen['suma']
form=ZamowienieForm(initial={'imie_nazwisko':user_dane, 'kwota_do_zaplaty':suma_wszystkich_cen})
but this is working only when request.method is POST.
if request.method =='POST':
form = ZamowienieForm()
According to documentation I shouldn't initial a empty form with POST... Is there any chance to have a user full name into a form?
Here is the form class:
class ZamowienieForm(forms.ModelForm):
class Meta:
model = Zamowienie
fields = ('imie_nazwisko', 'kwota_do_zaplaty', 'miejscowosc',
'ulica','numer_domu', 'numer_mi‌​eszkania', 'kod_pocztowy',)
class NewMeta:
readonly = ('imie_nazwisko','kwota_do_zaplaty',)
Maybe try something like this inside ZamowienieForm class
def __init__(self, *args, **kwargs):
super(ZamowienieForm, self).__init__(*args, **kwargs)
self.fields['imie_nazwisko'] = self.initial.get('imie_nazwisko')
self.fields['kwota_do_zaplaty'] = self.initial.get('kwota_do_zaplaty')
Although I don't understand why "initial" is not working out of the box
In this case, you only need to initialize your form once, and not inside a conditional check if the request is a GET or POST:
def your_view(request):
form = ZamowienieForm(
request.POST or None,
initial={'imie_nazwisko': request.user.get_full_name(),
'kwota_do_zaplaty': suma_wszystkich_cen}
)
if request.method == 'POST' and form.is_valid():
# do whatever
This way you are always passing in the initial value, and if request.method == 'GET', then None is passed as the first positional argument to the form.
Also, user.get_full_name is an instance method, not a property, so using request.user.get_full_name only returns the bound method, not the actual value. You have have to call the function using ()
Finally, this will only work for users that are authenticated. The anonymous user object in Django won't return any user-specific information.

Multiple linked Django forms, one submit

I have models for Application and Role. Role is linked to a FK Role_type, which is linked by FK to Applications that can use those Role_types (this is a bit of an over-simplication for the question, but I think it suffices). I need a way to create a form to make a new Application, and also to create records assigning associated roles to people (although they can be left blank.)
I have gotten as far as creating the form for the Application and having the associated Role-Types appear on the page, with dropdowns to be populated with a user. Hitting submit, though, didn't create any of the associated Role records. All of my research seems to keep coming back to Inline Model Forms, but the docs aren't really making sense to me--the inputs in the example don't seem to correlate to what I need.
I know this may seem like a duplicate, but trust me when I say I've looked at every SO question that seems to relate to this!
EDIT: My POST looks like this: QueryDict: {u'roles-MAX_NUM_FORMS': [u'1000'], u'roles-1-role_type': [u'4'], u'roles-0-user': [u'1'], u'app-owner': [u'1'], u'app-name': [u'1234'], u'app-serviceTier': [u''], u'app-jiraProject': [u''], u'roles-TOTAL_FORMS': [u'2'], u'roles-1-user': [u''], u'roles-0-role_type': [u'3'], u'csrfmiddlewaretoken': [u'eGsDwtsSQJfl0'], u'roles-INITIAL_FORMS': [u'2']}>. Printing RolesFormSet gives me the exact same output (see comment below)
models.py
class Item(models.model):
name = models.CharField(max_length=255)
roles = models.ManyToManyField(User, through='Role')
class Application(Item):
other_assorted_attributes = foo
class RoleType(models.Model):
name = models.CharField(max_length=255)
class ItemTypeRoleMapping(models.Model):
''' pairs role-types (e.g., Developer) with an Item class they are relevant to'''
roleType = models.ForeignKey(RoleType)
itemType = models.CharField(max_length=255, choices=itemChoices)
class Role(models.Model):
role_type = models.ForeignKey(RoleType)
user = models.ForeignKey(User)
item = models.ForeignKey(Item)
views.py
def buildRolesFormset(itemClass):
''' given an item, build a form for all associated roles '''
roleTypesForItem = ItemTypeRoleMapping.objects.all().filter(itemType=itemClass.__name__)
applicable_roles = [{'role_type': roleType} for roleType in roleTypesForItem]
# formset = rolesFormSet(initial=initial, prefix='roles')
RoleFormSet = inlineformset_factory(Application, Role, extra=len(roleTypesForItem), can_delete=False)
formset = RoleFormSet()
for subform, data in zip(formset.forms, applicable_roles):
subform.initial = data
return formset
def new(request):
''' Create a new application '''
user = request.user
# check permission
if request.method == 'POST':
appform = AppForm(request.POST, prefix='app')
if appform.is_valid():
app = appform.save(commit=False)
rolesInlineFormSet = inlineformset_factory(Application, Role)
# pdb.set_trace()
rolesFormSet = rolesInlineFormSet(request.POST, instance=app, prefix='roles')
if rolesFormSet.is_valid():
rolesFormSet.save()
else:
print rolesFormSet.errors
app = appform.save()
# check rolesFormSet
return redirect(reverse('index'))
else:
appform = AppForm(prefix='app')
rolesFormSet = buildRolesFormset(Application)
return render(request, 'who/editapp.html',
{'appform': appform,
'rolesFormSet': rolesFormSet
})
Tricky to tell without more information, but it looks like you're not saving your rolesFormset in the view. You need to call rolesFormset.save() alongside your form.save() call. Additionally, I suppose you want to attach the roles to the created app? Something like this in your view should work:
if request.method == 'POST':
form = AppForm(request.POST)
rolesFormset = RoleForm(request.POST)
if form.is_valid() and rolesFormset.is_valid():
app = form.save()
roles = rolesFormset.save()
for role in roles:
app.roles.add(role)
return redirect(reverse('index'))
Update: Presuming the models.py is out-of-date, and Role does in fact have a foreignKey to User, the problem will be that you're setting a prefix here:
rolesFormSet = rolesInlineFormSet(request.POST, instance=app, prefix='roles')
but not in your buildRolesFormset function. In that function, do:
formset = RoleFormSet(prefix='roles')

Categories

Resources