Sorry the title isn't very descriptive. The context: I have an event full of participants. When the event is over I want to leave feedback for all of the other participants.
models.py
class Feedback(models.Model):
action = models.ForeignKey(Action)
feedback_by = models.ForeignKey(UserProfile, related_name='feedback_by')
feedback_for = models.ForeignKey(UserProfile, related_name='feedback_for')
comment = models.CharField(max_length=200)
no_show = models.BooleanField()
created = models.DateTimeField()
modified = models.DateTimeField()
forms.py
class FeedbackFormSet(BaseModelFormSet):
def add_fields(self, form, index):
super(FeedbackFormSet, self).add_fields(form, index)
form.fields['is_checked'] = forms.BooleanField(required=False)
class FeedbackForm(forms.ModelForm):
comment = forms.CharField(label=(u"Comment"), widget=forms.Textarea())
class Meta:
model = Feedback
fields = ['comment', 'no_show']
I want to create a feedback page, where there would be one instance of the FeedbackForm for each participant. After some searching it seems that to do that I want to be using a FormSet, but I'm not finding the documentation for it very helpful and I can't seem to find any good examples.
If a formset is the way to go, could you guys help me out with some (view/formset basically) starter code? If not, can you point me to what I should be doing? Thanks.
EDIT: I've added my view and template code below.
views.py
#login_required
def new_feedback(request, action_id):
action = get_object_or_404(Action, id=action_id)
profile = UserProfile.objects.get(user_id=request.user.id)
participants = all_info_many_profiles(action.participants.filter(~Q(id=profile.id)))
fbformset = modelformset_factory(Feedback, form=FeedbackForm, formset=FeedbackFormSet)
if request.method == 'POST':
formset = fbformset(request.POST, request.FILES, queryset=action.participants.filter(~Q(id=profile.id)))
if formset.is_valid():
formset.save()
else:
print formset.errors
else:
formset = fbformset(queryset=action.participants.filter(~Q(id=profile.id)))
return render(request, 'app/new_feedback.html',
{'action': action, 'participants': participants, 'formset': formset}
new_feedback.html
{% block body_block %}
<h1>Leave Feedback</h1>
{% for participant in participants %}
<li>{{ participant.username }}</li>
{% endfor %}
<form method="post" action="">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
{{ form.as_p }} <br />
{% endfor %}
<input type="submit" name="submit" value="Submit Feedback" />
</form>
{% endblock %}
What I'm trying to achieve is to associate one form to each user in the list (or queryset) of participants. What I currently have shows one more form than I want (for example, when I list each user I'll see 3 names but 4 forms example) and I don't know if or how the users/forms are related.
The idea is that the feedback_for field will get its value automatically, ie in the view I would do:
if formset.is_valid():
for form in formset:
a = form.save(commit=false)
a.feedback = participant
a.save()
On top of that, I added an extra field "is_checked" which is intended to specify whether I'm leaving feedback for that user or not. Example of full functionality:
user1 [X] is_checked
... rest of form
user2 [] is_checked
... rest of form
user3 [X] is_checked
... rest of form
Then when I hit submit it will create two new entries in the Feedback table, one for user1 and one for user3
Related
I'm building a page that allows users to edit Task and related Activity records (one task can have many activities), all on the same page. Here are extracts from my code...
models.py
from django.contrib.auth.models import User
class Task(models.Model):
category = models.CharField(max_length=300)
description = models.CharField(max_length=300)
class Activity(models.Model):
task = models.ForeignKey(Task, on_delete=models.CASCADE)
title = models.CharField(max_length=150)
notes = models.TextField(blank=True)
owner = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
The activity "owner" is linked to a User from the Django standard user model.
views.py
def manage_task(request, pk):
task = Task.objects.get(pk = pk)
TaskInlineFormSet = inlineformset_factory(Task, Activity,
form = ActivityForm)
if request.method == "POST":
form = TaskForm(request.POST, instance = task)
formset = TaskInlineFormSet(request.POST, instance = task)
if form.has_changed() and form.is_valid():
form.save()
if formset.has_changed() and formset.is_valid():
formset.save()
return redirect('manage_task',pk=task.id)
else:
form = TaskForm(instance = task)
formset = TaskInlineFormSet(instance = task)
context = {'task': task, 'task_form': form, 'formset': formset}
return render(request, 'tasks/manage_task.html', context)
And manage_task.html excerpt:
<h2>{{ task.category }}</h2>
<form method="post">
{% csrf_token %}
{{ task_form.description }}
{% for form in formset %}
{{ form.id }}
{{ form.title }}</br>
{{ form.notes }}</br>
{% if user.id == form.owner.value %}
You own this Activity!</br>
{% else %}
{{ form.owner.first_name }} owns this Activity</br>
{% endif %}
{% endfor %}
<input class="save" type="submit" value="SAVE">
</form>
Perhaps obviously, {{ form.owner.first_name }} doesn't return anything. form.owner understandably renders as a select drop down with all the Users listed - it's some sort of iterable object so it doesn't have a first_name attribute.
I think I need to have the first_name field in each form in the formset so it ties up properly with the correct Activity form.
I feel like I might have to adapt the view in some way to go through all the forms in the formset and use the owner.id (if it exists) to access the related User and add the first_name attribute as an extra field in the form somehow? That doesn't feel very Djangoish, there must be a better way.
How can I use the form field form.owner to get the first_name attribute from the User object that's related to the Activity so I can use it in the template?
You should not take such value from form directly. It may be risky (i.e. changing real-life). But if you are aware of it and ok with that, try pointing to specific instance of form:
{{ form.instance.owner.first_name }}
Note: I'm new to Django and Python. I'm not sure if I've misunderstood the use of inline formsets in some way.
I needed to be able to edit certain fields for multiple instances of a Guest model (linked to a parent Invite model) at the same time in one form. After a lot of back and forth, I managed to set up an inline formset that submits successfully.
However, the interface is confusing, because you don't know which Guest you're filling in details for. Each Guest has a guest_name field, and I just want to display that name for each form, so that users know who they're editing for.
Here's my (condensed) view:
def extra_view(request, code):
# Get the specific invite
invite = get_invite(code)
# Get the context from the request.
context = RequestContext(request)
# Store guests attending object
guests_attending = invite.guest_set.filter(attending=True, invite=invite)
# Create the formset for each Guest
GuestFormset = inlineformset_factory(Invite, Guest,
form=ExtraForm,
fields=('diet', 'transport'),
extra=0,
can_delete=False)
if request.method == "POST":
formset = GuestFormset(request.POST, request.FILES,
instance=invite,
queryset=Guest.objects.filter(attending=1))
if formset.is_valid():
# Save the data to the database.
formset.save()
# Redirect stuff here
if guests_attending.count() > 0:
formset = GuestFormset(instance=invite, queryset=Guest.objects.filter(attending=1))
# Return the view
return render_to_response('app/extra.html', {
'GuestForm': formset,
'invite': invite,
'guests_attending': guests_attending,
'errors': formset.errors
}, context)
else:
# Backup for if no guests are attending
Here's how the models look:
class Invite(models.Model):
# Code to identify invites by
code = models.CharField(max_length=6, default=code_generator(4, do_check=True), unique=True)
group_name = models.CharField(max_length=200)
def __str__(self):
return self.group_name
class Guest(models.Model):
invite = models.ForeignKey(Invite, on_delete=models.CASCADE)
guest_name = models.CharField(max_length=200)
diet = models.CharField(max_length=250, null=True, blank=True)
transport = models.NullBooleanField(default=False)
attending = models.NullBooleanField(default=False)
def __str__(self):
return self.guest_name
And here's my template
{% if invite %}
<form method="post" action="">
{% csrf_token %}
{{ GuestForm.management_form }}
<table>
{% for form in GuestForm %}
<!-- Which guest am I editing for?! -->
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for field in form.visible_fields %}
{{ field.errors }}
<div>
{{ field.help_text }}
{{ field }}
</div>
{% endfor %}
{% endfor %}
</table>
<button type="submit" class="btn">Continue</button>
</form>
{% endif %}
You can access the form's instance with form.instance. In the template, you could do something like:
{{ form.instance.guest_name }}
ok i am following the formset. Pretty much i understand that formset is for multiple form.
so this example i just want take four values same time but the ouput in html have only one form is showing.
Shall i want to make extra filed like this <input id="your_name" type="text" name="your_name"> or django will do the rest or any other way to do that.?
models.py
class Article(models.Model):
title = models.CharField(max_length=100)
pub_date = models.DateField(auto_now_add=True)
forms.py
class ArticleForm(forms.Form):
title = forms.CharField()
#pub_date = forms.DateField()
ArticleFormSet = formset_factory(ArticleForm, extra=4, validate_max=True)
views.py
def book(request):
if request.method == 'POST':
formset = ArticleForm(request.POST)
if formset.is_valid():
new = Article()
new.title = request.POST.get('title', None)
#new.pub_date = request.POST.get('pub_date', None)
new.save()
return HttpResponseRedirect(reverse('firstapp.views.book'))
else:
formset = ArticleForm()
return render_to_response('get.html',{'formset': formset}, context_instance = RequestContext(request))
And the html look like this
<form method="post" action="">
{% csrf_token %}
{{ formset.management_form }}
<table>
{% for form in formset %}
{{ form }}
{% endfor %}
</table>
<input type="submit"/>
</form>
In your view you are binding formset to an ArticleForm, not to an ArticleFormSet. Also you are only creating one single Article from it, and you're not even using the form properly (ie: you're getting the title directly from request.POST instead of getting it from your form's cleaned_data). Your view code should look something like this (caveat: untested and possibly buggy code, but at least you'll get the picture).
def book(request):
if request.method == 'POST':
formset = ArticleFormSet(request.POST)
if formset.is_valid():
for data in formset.cleaned_data:
Article.objects.create(title=data['title'])
return HttpResponseRedirect(reverse('firstapp.views.book'))
else:
formset = ArticleFormSet()
return render_to_response('get.html',{'formset': formset},
context_instance = RequestContext(request))
As a last point, I strongly suggest you have a look at ModelForms.
I am stuck with a problem - I want to add a simple form field to edit the objects that I am looping through in the template. Here's my model:
class Topic(BaseModel):
name = models.TextField()
Here's my model form:
class TopicForm(forms.ModelForm):
class Meta:
model = Topic
fields = ["name"]
And here's my views:
def App(request):
tname = Topic.objects.get(pk=1)
if request.method == "POST":
form = TopicForm(data = request.POST, instance=tname)
if form.is_valid():
form.save()
And my template is simple:
{% for lecture in lectures %}
<form action="/app/" method="POST">
{% csrf_token %}
{{ form }}
<input type="submit" value="Post">
</form>
{% endfor %}
The thing is that I want to have a form field to edit EACH model not just one that has a pk of 1... how do I do that ?
I think you need to do objects.all() instead of get(pk=1). Then loop over those objects, and save them to a list that you save to the context. Something like this:
tnames = Topic.objects.all()
lectures = []
for tname in tnames:
lectures.append(TopicForm(instance=tname))
context = {
'lectures' : lectures
}
More specifically, you probably want to look into Model formsets. See https://docs.djangoproject.com/en/1.6/topics/forms/modelforms/#changing-the-queryset. Then you can directly give a queryset as initial data.
I am getting this error when i visit my page:
Caught AttributeError while rendering: 'WSGIRequest' object has no attribute 'get'
The error kicks in on line "17" of my html, which is the line that outputs form.as_p
The html looks like this:
{% extends "base.htm" %}
{% block content %}
{% if story_list %}
{% for story in story_list %}
<div class="Story">
{{ story.title }}
</div>
{% endfor %}
{% else %}
<p>No stories are present - enter one below</p>
{% endif %}
<h3>Create a new story</h3>
<form action="/addStory" method="post">
{% csrf_token %}
{{ form.as_p }} ***THIS IS LINE 17***
<input type="submit" value="Submit"/>
</form>
{% endblock %}
The problem is i have a view that does two things, and from the django tutorials overrode the get_context_data method to add the second item to the django context. Because, um, that's what i'm meant to do, riiiiiiiiight?
#for showing of the stories!
class StoryShowView(ListView):
model = StoryForm
def get_queryset(self):
return getStoryItemsForUser(self.request)
def get_context_data(self, **kwargs):
context = super(StoryShowView,self).get_context_data(**kwargs)
context['form'] = createNewStoryForm(self.request)
return context
Where, well, the method createNewStoryForm just does this:
def createNewStoryForm(request):
return StoryForm(request)
and StoryForm is just this:
class StoryForm(ModelForm):
class Meta:
model = Story
ordering = ['create_date']
and the Story model is a normal model, that probably isn't part of the problem, but, hey, i am a cutting and a pasting, so here goes!
class Story(models.Model):
user = models.ForeignKey(User)
post = models.ForeignKey(Post)
title = models.CharField(max_length=100)
is_closed = models.BooleanField()
is_random = models.BooleanField() # for uncategorised stories. Only one of these.
result = models.CharField(max_length=20) #how did the relo work out?
create_date = models.DateTimeField('date created')
def __unicode__(self):
return self.title
Any ideas what i am doing wrong?
UPDATE:
ah, it was the line::
return StoryForm(request)
I take it i can either pass in a "request.POST" or nothing, is that it?
Probably you're right and you were passing request instead of request.POST, reqest.GET or request.REQUEST to the constructor of your form. See the doc on how to use forms:
def contact(request):
if request.method == 'POST': # If the form has been submitted...
form = ContactForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('/thanks/') # Redirect after POST
else:
form = ContactForm() # An unbound form
return render_to_response('contact.html', {
'form': form,
})
Two problems that I can see. The easy one being that you can simply replace this line:
context['form'] = createNewStoryForm(self.request)
with
context['form'] = StoryForm(request.POST, request.FILES)
Finally shouldn't this:
class StoryShowView(ListView):
model = StoryForm
Be:
class StoryShowView(ListView):
model = Story