Foreign key POST in Django - python

I have some problems with my app . I need to make task with relations to user.
Here is my code:
bug
ValueError at /
Cannot assign "(<SimpleLazyObject: <function AuthenticationMiddleware.process_request.<locals>.<lambda> at 0x04C54E38>>,)": "Task.usertask" must be a "UserMembership" instance.
models.py
class UserMembership(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, null=True,blank=True)
image=models.FileField(upload_to='photos/%Y/%m/%d/',null=True,blank=True)
class Task(models.Model):
title=models.CharField(max_length=200)
date = models.DateTimeField(default=datetime.now,blank=True)
is_published=models.BooleanField(default=True)
usertask=models.ForeignKey(UserMembership, on_delete=models.PROTECT, null=True, blank=True)
views.py
if request.method == 'POST':
usertask = request.user,
title = request.POST['title']
task = Task(usertask=usertask,title=title)
task.save()
task = Task.objects.order_by('-date').filter(is_published=True, usertask=request.user.id)
context={
'task':task,
}
return render(request,'index.html',context)
html form
<form action="" method="POST">
{% csrf_token %}
<input name="title" type="text" class="form-control" id="usr">
<button type="submit" class="btn btn-primary">add</button>

Your task model's usertask field related with UserMembership model as ForeignKey.So you must give a UserMembership object for create Task object. You gave request.user as usertask but request.user is'nt a UserMembership object.You must find UserMembership object before. You can use following code:
user_member_ship = UserMemberShip.objects.get(user=request.user)
Task.objects.create(title=title,usertask=user_member_ship)

A few things:
1) in your views function you are doing this: usertask = request.user. Note that request.user will return a User object.
2) You then try to create a Task object by doing this task = Task(usertask=usertask,title=title) but here you have set usertask to a User object when you really need a UserMembership object
You can try rewriting your view function like so:
def MyViewFunction(request):
# unpack request:
user = request.user
title = request.POST['title']
# create new UserMembership object:
user_membership = UserMembership.objects.create(user=user)
user_membership.save()
# create new Task object:
task = Task.objects.create(usetask=user_membership, title=title)
task.save()
# continue code here...

As the error says your usertask field of your Task model refers to a UserMembership, not a User object.
You can use .get_or_create() [Django-doc] to retrieve, or create such object if it does not yet exists. For example:
def myview(request):
if request.method == 'POST':
usertask, __ = UserMembership.objects.get_or_create(
user=request.user
)
title = request.POST['title']
task = Task.objects.create(usertask=usertask, title=title)
task = Task.objects.order_by('-date').filter(
is_published=True,
usertask__user=request.user
)
context={
'task':task,
}
return render(request,'index.html',context)
You should filter on the usertask__user=request.user, since again, a usertask will retrieve a UserMembership object, not a User object.
I strongly advice to work with forms [Django-doc] and not try to process the request.POST querydictionary yourself. It is for example possible that the POST request does not contain any value for title, which can raise errors. A form will handle that more elegantly.

Related

Django: update a model with the current user when an non-model form field is changed

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. I want to allow the user to "adopt" one or more activities by ticking a box, and have their user record linked to each activity via a ForeignKey. 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.
I added an extra field in the form definition for the adopt field - I don't want to add it to the model as I don't need to save it once it's done it's job.
forms.py
class ActivityForm(forms.ModelForm):
adopt = forms.BooleanField(required=False)
class Meta:
model = Activity
fields = '__all__'
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():
## ? DO SOMETHING HERE ? ##
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)
When the adopt field is ticked on the form, I want to be able to set the owner field in that form to the current user before the associated model instance is updated and saved.
I just can't figure out how to do that - if it was a single form (rather than an InlineFormSet), I think I could put code in the view to change the owner value in the form field before it was saved (I haven't tried this). Or try save(commit = False) and update the model instance then save() it.
Maybe I have to iterate through the formset in the view code and try one of those options when I find one that had adopt=True?
When the adopt field is ticked on the form, I want to be able to set the owner field in that form to the current user before the associated model instance is updated and saved.
formset = TaskInlineFormSet(request.POST, instance = task)
if formset.adopt:
# If True
formset.user = request.user
formset.save()
I think I could put code in the view to change the owner value in the form field before it was saved (I haven't tried this).
You should give it a try.
I'm not happy with this solution but it does work. I iterate through the forms and change the object instance if my adopt field is set.
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():
## HERE'S WHAT I ADDED ##
for form in formset:
if form.cleaned_data['adopt'] is True:
form.instance.owner = request.user
## END OF ADDITIONS ##
formset.save()
## return redirect('manage_task',pk=task.id) # CHANGED THIS BECAUSE I WASN'T RETURNG ERRORS!
if not form.errors and not formset.total_error_count():
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)
I wish I could find more in the docs about how the form saving works but I think I'll have to look into the code if I want more detail.

Items not selected when using custom MultipleChoiceField

I am using a custom MultipleChoiceField, in order to display user's full names instead of their logins.
I customized the way items are displayed using the choices attribute of MultipleChoiceField:
assignees = MultipleChoiceField(choices=[(user.id, user.get_full_name() or user.username) for user in User.objects.all()])
Users names are correctly displayed, but the problem is that on edition (i.e. where the Form is given an instance of Story with existing assigned users), the corresponding <option> are not selected...
I tried using the initial keyword but that didn't helped.
self.fields['assignees'] = MultipleChoiceField(choices=[(user.id, user.get_full_name() or user.username) for user in User.objects.all()],
initial=[user.id for user in self.instance.assignees])
models.py
class Story(BaseModel):
assignees = models.ManyToManyField(User,
blank=True,
related_name="assigned")
forms.py
class StoryForm(forms.ModelForm):
assignees = MultipleChoiceField(choices=[(user.id, user.get_full_name() or user.username) for user in User.objects.all()])
views.py
def story_edit(request, story_id):
if request.method == 'POST':
story = get_object_or_404(Story, id=story_id)
form = StoryForm(request.POST, instance=story)
if form.is_valid():
form.save()
else:
story = get_object_or_404(Story, id=story_id)
form = StoryForm(instance=story)
return render(request, 'story_form.html', {'form': form})
story_form.html
<p>
{{ form.assignees.errors }}
{{ form.assignees }}
</p>

Django Model Form: show query form and handle post request for OneToMany fields

I am a newbie to Django and could not find similar questions after searching on google/SO.
I've a model named Questions, which has multiple(2-4) choices and defined as below:
class Question(models.Model):
name = models.CharField(max_length=128)
class Choice(models.Model):
name = models.CharField(max_length=256)
is_correct = models.BooleanField(default=False)
question = models.ForeignKey(Question, on_delete=models.CASCADE)
Of the multiple choices only one is correct.
What I want to do: In just one page, user could submit a question together with multiple choices, here is a draft of UI:
My first question: I've defined ModelForm but don't know how to add "choices" field to QuestionForm:
class QuestionForm(ModelForm):
name = forms.CharField(max_length=128)
description = forms.CharField(max_length=256)
class Meta:
model = Question
fields = ['name', 'description']
class ChoiceForm(ModelForm):
name = forms.CharField(max_length=256)
is_correct = forms.BooleanField()
class Meta:
model = Choice
fields = ['name', 'is_correct']
Is it possible to use ModelForm the render the above HTML page besides writing it manually?
My second question: If use clicks "Submit" button, I use AJAX to send json data to backend server, here is an example of form data:
name:question1
choices[][name]:choice1
choices[][is_correct]:1
choices[][name]:choice2
choices[][is_correct]:0
And this is my code handling the request:
form = QuestionForm(request.POST)
if form.is_valid():
question = form.save()
How to parse choices from the request?
How could I parse data of multiple choices part from the POST request?
Again, I'm a newbie to Django and any answers/suggestions is highly appreciated.
To create forms for models which have a OneToMany relation I would recommend you to use Django's inline formsets: https://docs.djangoproject.com/en/1.8/topics/forms/modelforms/#inline-formsets
It's a really simple and elegant way to create forms for related models.
To parse the choices, the user entered you could just override the clean method of your form. In this the user content is usually checked and prepared for storing it to the database. https://docs.djangoproject.com/en/1.8/ref/forms/validation/#form-field-default-cleaning
So cleaning could look like this:
class QuestionForm(ModelForm):
...
def clean(self):
cleaned_data = super(QuestionForm, self).clean()
if cleaned_data['choice_name'].startswith('Something'):
raise forms.ValidationError(
"Choice names cannot start with 'Something'!"
)
You models seems to be correct, in order to be able to add mutiple choices in your template you need a formset. In addition you can put a formset and a form inside the same html form in a template and have them be validated individually. Each one only cares about the POST data relevant to them. Something like:
template.html
<form method="post" action="">
{% csrf_token %}
{{ choices_formset.management_form }} <!-- used by django to manage formsets -->
{{ question_form.as_p }}
{% for form in choices_formset %}
{{ form.as_p }}
{% endfor %}
<button type='submit'>Submit</button>
</form>
views.py
from django.db import IntegrityError, transaction
from django.shortcuts import redirect
from django.forms.formsets import formset_factory
from django.core.urlresolvers import reverse
def new_question(request):
ChoicesFormset = formset_factory(ChoicesForm)
if request.method == 'POST':
question_form = QuestionForm(request.POST)
choices_formset = ChoicesFormset(request.POST)
if question_form.is_valid():
question = Question(**question_form.cleaned_data)
if choices_formset.is_valid():
question.save()
new_choice_list = list()
append_choice = new_choice_list.append
for form in choices_formset:
form.cleaned_data.update({'question': question})
append_choice(Choice(**form.cleaned_data))
try:
with transaction.atomic():
Choice.objects.bulk_create(new_choice_list)
except IntegrityError as e:
raise IntegrityError
return redirect(reverse('question-detail-view', kwargs={'id': question.id}))
def question_detail(request, id):
question_list = Question.objects.get(id=id)
return render(request, 'question_detail.html', {'question_list': question_list})
urls.py
url(r'^question/$', new_question, name='new-question-view'),
url(r'^question/(?P<id>\d+)/$', question_detail, name='question-detail-view'),
If you want to use rather Ajax submission rather than django form sumbission check this tutoriel.

django newbie. Having trouble with ModelForm

i'm trying to write a very simple django app. I cant get it to show my form inside my template.
<form ...>
{{form.as_p}}
</form>
it shows absolutely nothing. If I add a submit button, it only shows that.
Do I have to declare a form object that inherits from forms.Form ? Cant it be done with ModelForms?
[UPDATE]Solved! (apologize for wasting your time)
In my urls file I had:
(r'login/$',direct_to_template, {'template':'register.html'}
Switched to:
(r'login/$','portal.views.register')
And yes, I feel terrible.
Background:
I have a Student model, and I have a registration page. When it is accessed, it should display a textfield asking for students name. If the student completes that field, then it saves it.
#models.py
class Student(models.Model):
name = models.CharField(max_length =50)
#forms.py
class StudentForm (forms.ModelForm):
class Meta:
model = Student
So, here is my view:
def register(request):
if request.method == 'POST':
form = StudentForm(request.POST)
if form.is_valid():
form.save()
return render_to_response('/thanks/')
else:
student = Student()
form = StudentForm(instance =student)
return render_to_response('register.html',{'form':form})
The problem is in your view. You will have no existing student object to retrieve from the database. The following code sample will help you implement an "create" view.
As a side note, you might like using the direct_to_template generic view function to make your life a bit easier.
def add_student(request):
if request.method == 'POST':
form = StudentForm(request.POST)
if form.is_valid():
new_student = form.save()
return HttpResponseRedirect('/back/to/somewhere/on/success/')
else:
form = StudentForm()
return direct_to_template(request,
'register.html',
{'form':form})

Django Forms: Foreign Key in Hidden Field

My form:
class PlanForm(forms.ModelForm):
owner = forms.ModelChoiceField(label="",
queryset=Profile.objects.all(),
widget=forms.HiddenInput())
etc...
class Meta:
model = Plan
Owner, in the model, is a ForeignKey to a Profile.
When I set this form, I set the value of "owner" to be a Profile object.
But when this comes out on the form, it seems to contain the name of the Profile like this:
<input type="hidden" name="owner" value="phil" id="id_owner" />
When the form is submitted and gets back to my views.py I try to handle it like this:
form = PlanForm(request.POST)
...
if form.is_valid():
plan = form.save()
return HttpResponseRedirect('/plans/%s'%plan.id) # Redirect after POST
However, what I get is a type-conversion error as it fails to turn the string "phil" (the user's name that was saved into the "owner" field) into an Int to turn it into the ForeignKey.
So what is going on here. Should a ModelForm represent a foreign key as a number and transparently handle it? Or do I need to extract the id myself into the owner field of the form? And if so, how and when do I map it back BEFORE I try to validate the form?
I suspect that the __unicode__ method for the Profile model instance, or the repr thereof is set to return a value other than self.id. For example, I just set this up:
# models.py
class Profile(models.Model):
name = models.CharField('profile name', max_length=10)
def __unicode__(self):
return u'%d' % self.id
class Plan(models.Model):
name = models.CharField('plan name', max_length=10)
profile = models.ForeignKey(Profile, related_name='profiles')
def __unicode__(self):
return self.name
# forms.py
class PlanForm(forms.ModelForm):
profile = forms.ModelChoiceField(queryset=Profile.objects.all(),
widget=forms.HiddenInput())
class Meta:
model = Plan
# views.py
def add_plan(request):
if request.method == 'POST':
return HttpResponse(request.POST['profile'])
profile = Profile.objects.all()[0]
form = PlanForm(initial={'profile':profile})
return render_to_response('add_plan.html',
{
'form':form,
},
context_instance=RequestContext(request))
With that, I see PlanForm.profile rendered thus in the template:
<input type="hidden" name="profile" value="1" id="id_profile" />
Hmm...
This might actually be a security hole.
Suppose a malicious attacker crafted a POST (say, by using XmlHttpRequest from FireBug) and set the profile term to some wacky value, like, your profile ID. Probably not what you wanted?
If possible, you may want to get the profile from the request object itself, rather than what's being submitted from the POST values.
form = PlanForm(request.POST)
if form.is_valid():
plan = form.save(commit=False)
plan.owner = request.user.get_profile()
plan.save()
form.save_m2m() # if neccesary
When you assign a Profile object to the form, Django stringifies it and uses the output as the value in the form. What you would expect though, is for Django to use the ID of the object instead.
Luckily, the workaround is simple: Just give the form primary key values of the Profile objects instead:
form = PlanForm(initial={'profile': profile.pk})
On the other end, when you're working with bound forms, however, they work much more sensibly:
form = PlanForm(request.POST)
if form.is_valid():
print form.cleaned_data['profile'] # the appropriate Profile object
There's usually no need to put related object into form field. There's a better way and this is specifying parent id in form URL.
Let's assume you need to render a form for new Plan object and then create one when form is bubmitted. Here's how your urlconf would look like:
(r"/profile/(?P<profile_id>\d+)/plan/new", view.new_plan), # uses profile_id to define proper form action
(r"/profile/(?P<profile_id>\d+)/plan/create", view.create_plan) # uses profile_id as a Plan field
And if you're changing existing object, all you need is plan_id, you can deduce any related record from it.
Since ModelChoiceField inherits from ChoiceFIeld, you should use the MultipleHiddenInput widget for this:
class PlanForm(forms.ModelForm):
owner = forms.ModelChoiceField(
queryset=Profile.objects.all(),
widget=forms.MultipleHiddenInput())
class Meta:
model = Plan

Categories

Resources