Auto filling a form's foreign key - django-forms

So, i have been having problems filling in the user in a form. My strategy has been validating the form, creating an object, passing the foreign key and the other fields with .cleaned_data and saving the object.
More specifically, I basically want to create a "workout" object in the database, adding a name and a description from the form and autofilling the created_by_user field.
views.py
#login_required(login_url='login')
def workouts(request, user_id):
context = {}
if request.method == 'POST':
form = WorkoutForm(request.POST)
if form.is_valid():
name = form.cleaned_data['name']
description = form.cleaned_data['description']
Workout.objects.create(
created_by_user=request.user, name=name, description=description)
context = {'name': name, 'description': description}
return render(request, 'Trainmate/fill_workout_with_sets.html', context)
else:
form = WorkoutForm()
workout_programs = Workout.objects.all()
user_workouts = workout_programs.filter(created_by_user=user_id)
context = {'user_workouts': user_workouts, 'form': form}
return render(request, 'Trainmate/workouts.html', context)
models.py (I am just adding the workout model)
class Workout(models.Model):
name = models.CharField(max_length=150, null=True)
created_by_user = models.ForeignKey(User, null=True, on_delete=models.RESTRICT)
description = models.TextField(max_length=1000, null=True)
def __str__(self):
return self.name
forms.py
class WorkoutForm(forms.ModelForm):
class Meta:
model = Workout
fields = ['name', 'description', 'created_by_user']
my template
{% extends 'Trainmate/main.html' %}
{% block content %}
<h1>My Workouts</h1>
<div>
{% for workout in user_workouts %}
{{ workout.name }}<br>
{% endfor %}
</div>
<h1>Create new Workout</h1>
<form method="POST" action="">
{% csrf_token %}
{{ form.name }}
{{ form.description }}
<input type="submit" value="Create Workout">
</form>

You can remove 'created_by_user' from the fields list in your form as you do not need it to display or validating. Then render the form just with {{ form }}.
You can use save() method since you use a ModelForm. Change your view accordignly:
#login_required(login_url='login')
def workouts(request, user_id):
context = {}
if request.method == 'POST':
form = WorkoutForm(request.POST)
if form.is_valid():
workout = form.save(commit=False)
workout.created_by_user = request.user
workout.save()
context = {'name': name, 'description': description}
return render(request, 'Trainmate/fill_workout_with_sets.html', context)
else:
form = WorkoutForm()
workout_programs = Workout.objects.all()
user_workouts = workout_programs.filter(created_by_user=user_id)
context = {'user_workouts': user_workouts, 'form': form}
return render(request, 'Trainmate/workouts.html', context)

Related

How to select and save foreign key object in django views

I am new to django and was trying to select and save a foreign key object from model ProcessCategory.name in the ProcessGroup view . It works fine in admin, but i couldn't get it to work in my html.
Here's the model, view and html code.
can anyone please help?
models.py:
class ProcessCategory(models.Model):
name = models.CharField(max_length=200, primary_key=True)
desc = models.CharField(max_length=250, null=True, blank=True)
def __str__(self):
return self.name
class ProcessGroup(models.Model):
proc_category = models.ForeignKey(ProcessCategory, on_delete=models.CASCADE)
name = models.CharField(max_length=200, primary_key=True)
desc = models.CharField(max_length=250, null=True, blank=True)
def __str__(self):
return self.name
views.py:
def ProGrp(request):
p_category = ProcessCategory.objects.all()
return render(request, 'manage_group.html', {'p_category': p_category})
def ProGrp_save(request):
if request.method != "POST":
return HttpResponse("Method Not Allowed")
else:
pro_cat = request.POST.get("pro_cat")
pro_group = request.POST.get("add_pro_group")
pro_desc = request.POST.get("add_pro_desc")
process_category = ProcessGroup(
proc_category=pro_cat)
process_group = ProcessGroup(name=pro_group)
process_desc = ProcessGroup(desc=pro_desc)
process_category.save()
process_group.save()
process_desc.save()
messages.success(request, "Successfully Added Process Group")
return HttpResponseRedirect('add_pro_grp')
urls.py:
urlpatterns = [
path('add_pro_grp', views.ProGrp, name='add_pro_grp'),
path('add_progrp_save', views.ProGrp_save, name='add_progrp_save'),
]
HTML
<select class="form-control" name = 'pro_cat' placeholder="Select Process Category">
{% for each in p_category %}
<option value = {{each.name}} > {{each.name}} </option>
{% endfor %}
</select>
My answer assumes that you only want to create one ProcessGroup with all three fields populated.
I would suggest using ProcessGroup.objects.create [docs]. This will create and save the ProcessGroup for you all in one statement.
This will correctly set the proc_category foreign key for you if pro_cat is the primary key (pk [docs]) of a ProcessCategory or is an instance of ProcessCategory.
process_group = ProcessGroup.objects.create(
proc_category=pro_cat,
name=pro_group,
desc=pro_desc,
)
Edit for HTML solution
The value= on the <option> is what gets passed to the POST data, so to ensure that the save view is getting a value it can save just change your option tag a little bit. Switch each.name to each.pk to give your view the value it needs.
<option value="{{ each.pk }}"> {{ each.name }} </option>

Select a valid choice. <some choice> is not one of the available choices

I am trying to build a Form which will show a choice field with options. Now these options changes for different kind of user. I am able to set the values in the choice field from view. The problem is when I click on "Submit" all the fields become empty and get the error like "Select a valid choice. 2 is not one of the available choices."
Logic to find the correct list of companies to show in the choice field.
def get_my_companies(user):
if user.profile.company.is_customer:
return (user.profile_set.company.id,user.profile_set.company.name)
elif user.is_superuser:
companies = Company.objects.filter(is_customer=True)
list_of_companies = [(c.id, c.name) for c in companies]
return list_of_companies
else:
list_of_groups = list(user.groups.values_list('name',flat = True))
list_of_companies = []
# ... some more logic to find appropriate list of companies for each such user
return list_of_companies
The logic is working correctly and the values are getting populated on the page load. Only problem occurs when I submit the form.
views.py
def home(request):
my_companies = get_my_companies(request.user)
myForm = device_readings_form()
myForm.fields['company'].choices = my_companies
if request.method == 'POST':
myForm = device_readings_form(request.POST)
is_historical = True
if myForm.is_valid():
cd = myForm.cleaned_data
company = cd.get('company')
#device = cd.get('device')
date_from = cd.get('date_from')
date_to = cd.get('date_to')
print(date_from)
print(date_to)
# .... some more stuff to work. Then I return the render using template
context = {
'readings': data,
'columns' : df.columns,
'range' : {
'from' : datetime.datetime.now().strftime('%Y-%m-%d')+ ' 00:00:00',
'to': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
},
'is_historical' : is_historical,
'show_chart' : show_chart,
'form' : myForm
}
return render(request,'portal/home.html',context)
forms.py
class device_readings_form(forms.Form):
initial_from_date = datetime.date.today()
initial_to_date = initial_from_date + datetime.timedelta(days=1)
date_from = forms.DateField(initial=initial_from_date)
date_to = forms.DateField(initial=initial_to_date)
company = forms.ChoiceField()
portal/home.html
{% block sidebar %}
<div class="col-md-4">
<div class="content-section">
<p class='text-muted'> <h5>Get Historical Readings</h5>
<form method="POST" action="{% url 'portal-home' %}">
{% csrf_token %}
{{form|crispy}}
<button type="submit">Submit</button>
</form>
</p>
</div>
</div>
{% endblock sidebar %}
The problem is faced only in case of ChoiceField. If I remove the ChoiceField and keep only remaining two DateFields then form gets properly submitted and I am able to read the form values in the view.
Can you guys please point me in right direction what I am doing wrong?
I would like to get the form submitted properly and get the form values in the view to process.
SOLVED!!!
The solution is:
One need to set the choices in the form instance.
Modified the code like below:
forms.py
class device_readings_form(forms.Form):
company = forms.ChoiceField()
devices = forms.ChoiceField()
initial_from_date = datetime.date.today()
initial_to_date = initial_from_date + datetime.timedelta(days=1)
date_from = forms.DateField(initial=initial_from_date)
date_to = forms.DateField(initial=initial_to_date)
def __init__(self, companies, devices, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['company'].choices = companies
self.fields['devices'].choices = devices
views.py
def home(request):
my_companies = get_my_companies(request.user)
my_devices, total_list = get_available_devices(my_companies)
is_historical = False
show_chart = False
myForm = device_readings_form(my_companies, total_list)
#myForm.fields['company'].choices = my_companies
if request.method == 'POST':
myForm = device_readings_form(my_companies, total_list, request.POST or None)
is_historical = True
if myForm.is_valid():
cd = myForm.cleaned_data
company = cd.get('company')
device = cd.get('devices')
date_from = cd.get('date_from')
date_to = cd.get('date_to')
print(company)
print(device)
print(date_from)
print(date_to)

How to get a Modelform to show fields in a detail page in Django?

What I would like to accomplish is that I get the form from songcreate to in same url as the Detail.html so a person can add more songs without having to leave and comeback multiple times.Eventually I hope to do this process with AJAX but haven't advance to that stage yet since i can't get the form to start. I think it may just be something simple but have been unable to pin point what it is.
Views.py
class DetailView(generic.DetailView):
# form_class = SongCreate
model = Album
template_name = 'post/detail.html'
def get_form_kwargs(self):
kwargs = super(SongCreate, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def get_object(self, *args, **kwargs):
pk =self.kwargs.get('pk')
obj = get_object_or_404(Album, pk=pk)
return obj
def get_context_data( self, *args, **kwargs ):
context =super(DetailView, self).get_context_data(*args, **kwargs)
# context['create_form'] = SongCreate()
# context['create_url'] = reverse_lazy("post:index")
query = self.request.GET.get("q")
if query:
qs = Album.objects.search(query)
return context
This is the Song create view when using its on url. it works just fine but I want to be able to show this form in the detail page or list page if thats not possible.
class SongCreate(CreateView):
model = Song
success_url = reverse_lazy('post:index')
form_class = SongCreate
def form_valid(self, form):
if form.is_valid():
----------(Here is validation which ill skip for brevity)--------
return super(SongCreate, self).form_valid(form)
def get_form_kwargs(self):
kwargs = super(SongCreate, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
So my main problem lies here form some reason I am not able to get the fields to show up in the detail page correctly even though it shows up perfectly in its actual page. When I set it up it only shows me the the outline of the form and the submit button.
Details.html
{% block body %}
**<!--Form--> HERE IS WHERE I WANT THE FORM-->**
<div class='col-sm-9 '>
{% if not request.GET.q %}
<div class=''>
{% include "post/song_form.html" with form=create_form action_url=create_url btn_title='submit' form_class='SongCreate' %}
</div>
<hr/>
{% endif %}
<div clas="col-sm-8 col-sm-offset-2" >
{% for Song in Album.song_set.all %}
<div class="media">
<div class="media-left">
<a href="#">
{% if object.image %}
<img class="media-object" src="..." alt="...">
{% endif %}
</a>
</div>
<div class="media-body">
{{ song.song_name }}<br/>
{{ song.description}}|
via {{ Album.timestamp }}
</br/>
<br>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endblock %}
Here is my form i used a modelform and it requires you to select the the album and it shows the albums the person has created to you can append it that album then make a description about it.
Forms.py
from django.views.generic import DetailView
from .models import Song
from .models import Album
from django import forms
class SongCreate(forms.ModelForm):
class Meta:
model = Song
fields = [
'album',
'song_name',
'description'
]
def __init__(self, user=None, *args, **kwargs):
self.user = user
super(SongCreate, self).__init__(*args, **kwargs)
self.fields['album'].queryset = Album.objects.filter(owner=user)
Try include "/post/song_form.html". Without the preceding slash it might be looking in the local folder of your details.html.
Instead of doing two separate views for your detail view page and your form, you could include the form in the detail view page view. You could do this by adding a "mixin" FormMixin. So you would list your validate and create form logic in your detail view.
Link to docs here
from django.views.generic.edit import FormMixin
class DetailView(generic.DetailView,FormMixin):
model = Album
template_name = 'post/detail.html'
form_class = SongCreate
def get_object(self, *args, **kwargs):
pk =self.kwargs.get('pk')
obj = get_object_or_404(Roote, pk=pk)
return obj
def get_context_data( self, *args, **kwargs ):
context =super(DetailView, self).get_context_data(*args, **kwargs)
# context['create_form'] = SongCreate()
# context['create_url'] = reverse_lazy("post:index")
query = self.request.GET.get("q")
if query:
qs = Album.objects.search(query)
return context
**def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)

How to correctly reference the User model in a form

Basically I'm trying to offer a feature for my site where one can register an electric imp(more than one) to their user account. My issue is that my Device model, and by extension my DeviceForm ModelForm uses a ForeignKey to reference the user object. So when I go to register a device it asks for the name of the device, the imp's agent_id, the device type and the user to associate with the account. I think where my is_valid() validation is failing is with how I reference the User model. I don't think its a "valid" input for the user object. Is there a different way to use the User's username to link the device to the correct account?
Models.py:
class Device(models.Model):
name = models.CharField(max_length=100)
agent_id = models.CharField(max_length=100)
device_type = models.CharField(max_length=100, choices=(
("imp", "Electric Imp P3V3"),
))
owner = models.ForeignKey(User)
def __unicode__(self):
return self.name
class DeviceForm(ModelForm):
class Meta:
model = Device
fields = ['name', 'agent_id', 'device_type', 'owner']
def clean_agent_id(self):
agent_id = self.cleaned_data['agent_id']
if Device.objects.exclude(pk=self.instance.pk).filter(agent_id=agent_id).exists():
raise forms.ValidationError(u'agent_id "%s" is already in use.' % agent_id)
return agent_id
Views.py:
def devices(request):
devform = DeviceForm(request.POST)
if devform.is_valid():
device_obj = devform.save()
device_obj.save()
return HttpResponseRedirect('deviceconfirmation')
else:
devform = DeviceForm()
return render_to_response('courses/devices.html', {'devform': devform}, context_instance=RequestContext(request))
devices.html:
{% extends "layout.html" %}
{% load static from staticfiles %}
{% block title %}{{ page.title }}{% endblock %}
{% block content %}
<article>
<div id="wrapper">
<p id="devicecreate">Register your device to your account:</p>
<form action="{% url 'courses:deviceconfirmation' %}" id="devform" method="post"> {% csrf_token %}
<p>
<label for="name">Device Name:</label>
<input id="name" name="name" type="text">
</p>
<p>
<label for="agent_id">Agent ID:</label>
<input id="agent_id" name="agent_id" type="text">
</p>
<p>
<label for="device_type">Imp Type:</label>
<select name="device_type" form="devform" id="selectbox">
<option value="imp">Imp Regular</option>
<option value="Electric Imp P3V3">Imp P3V3</option>
</select>
</p>
<p>
<label for="owner">Device Owner(username):</label>
<input id="owner" name="owner" type="text">
</p>
<p>
<input type="submit" value="REGISTER DEVICE" id="submit">
</p>
</form>
</div>
</article>
{% endblock %}
views.py for device confirmation:
def deviceconfirmation(request):
if request.method == 'POST':
try:
dev = Device.objects.get(agent_id=request.POST['agent_id'])
return render(request, 'courses/deviceconfirmation.html', {'dev': dev})
except Device.DoesNotExist:
return HttpResponseRedirect('invalidimp')
else:
raise Http404('Only POSTs are allowed')
urls.py:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.homepage, name='homepage'),
url(r'contact/$', views.contact, name='contact'),
url(r'login/$', views.login, name='login'),
url(r'products/$', views.products, name='products'),
url(r'register/$', views.register, name='register'),
url(r'register/thanks/$', views.thanks, name='thanks'),
url(r'register/inuse/$', views.inuse, name='inuse'),
url(r'login/accountinfo/$', views.accountinfo, name='accountinfo'),
url(r'devices/$', views.devices, name='devices'),
url(r'devices/deviceconfirmation/$', views.deviceconfirmation, name='deviceconfirmation'),
url(r'devices/deviceconfirmation/invalidimp/$', views.invalidimp, name='invalidimp'),
]
For when the logged in user adds a device to himself only: You can pass the current user as owner to the Form when you post. You don't need the owner initially in the form if not posting. In the form, override the save method and pass the owner before saving the device instance.
--
update based on comments:
amends to url for deviceconfirmation and view; url now accepts the device id. See bellow code updated
see also the template updated info
View (assuming def devices is used for both get and post):
def devices(request):
if request.method == 'POST":
devform = DeviceForm(request.POST, owner=request.user)
if devform.is_valid():
dev = devform.save()
return HttpResponseRedirect(reverse('deviceconfirmation', kwargs={'device_id': dev.id}))
else:
return render_to_response('courses/devices.html', {'devform': devform}, context_instance=RequestContext(request))
else:
devform = DeviceForm()
return render_to_response('courses/devices.html', {'devform': devform}, context_instance=RequestContext(request))
View deviceconfirmation:
def deviceconfirmation(request, device_id=None):
try:
dev = Device.objects.get(id=device_id)
return render(request, 'courses/deviceconfirmation.html', {'dev': dev})
except Device.DoesNotExist:
return HttpResponseRedirect('invalidimp')
Form:
class DeviceForm(ModelForm):
class Meta:
model = Device
fields = ['name', 'agent_id', 'device_type']
def __init__(self, *args, **kwargs):
owner = kwargs.pop('owner', None)
super(DeviceForm, self).__init__(*args, **kwargs)
self.owner = owner;
def clean_agent_id(self):
agent_id = self.cleaned_data['agent_id']
if Device.objects.filter(agent_id=agent_id).exists():
raise forms.ValidationError(u'agent_id "%s" is already in use.' % agent_id)
return agent_id
def save(self, commit=True):
device = super(DeviceForm, self).save(commit=False)
device.owner = self.owner
if commit:
device.save()
return device
Template:
delete the form action url in the template -> when you post, it will go to the same view from which you did the get (which is the devices view) ; <form action="" .....>
remove owner form field - to display current owner username, just use
{{request.user.username}}
URL:
url(r'devices/deviceconfirmation/(?P<device_id>\S+)/$', views.deviceconfirmation, name='deviceconfirmation'),

Getting ModelForm to insert hidden field for id (preventing duplicate instance on save)

I'm trying to build a simple CUD (Create/Update/Delete) view with a ModelForm, but on save I'm getting a duplicate record:
ModelForm:
class formFacetAnswer(forms.ModelForm):
class Meta:
model = models.FacetAnswer
exclude = ('who')
View:
def xxx_test1(request):
if request.method == 'POST':
form = formFacetAnswer(request.POST)
if form.is_valid():
answer = form.save(commit=False)
answer.who = request.user
answer.save()
return HttpResponseRedirect('/')
else:
a_id = request.GET.get('answer')
if a_id:
a_id=int(a_id)
answer = models.FacetAnswer.objects.get(id=a_id)
form = formFacetAnswer(instance=answer)
else:
form = formFacetAnswer()
return render_to_response('facet_answer.html', dict(form=form), context_instance=RequestContext(request))
Template:
{% extends 'head-plain.html' %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
{% endblock %}
The formFacetAnswer(instance=answer) does not insert a hidden field with the answer ID, so at save time it creates a new one. What's the best pattern to use for this need, under Django 1.3?
Currently you are only passing the instance on GET. If you want to update an existing record on save then you should pass the instance on POST as well.
The "obvious" way when updating ModelForms seems to be having the identifier to the instance of the object in the url.
urls.py:
url(r'^answer/(\d+)$', 'app.views.xxx_test1', name='editAnswer'),
url(r'^answer/$', 'app.views.xxx_test1', name='newAnswer '),
View:
def xxx_test1(request, a_id=None):
if request.method == 'POST':
if a_id:
answer = models.FacetAnswer.objects.get(id=a_id)
form = formFacetAnswer(request.POST, instance=answer)
else:
form = formFacetAnswer(request.POST)
if form.is_valid():
answer = form.save(commit=False)
answer.who = request.user
answer.save()
return HttpResponseRedirect('/')
else:
if a_id:
answer = models.FacetAnswer.objects.get(id=a_id)
form = formFacetAnswer(instance=answer)
else:
form = formFacetAnswer()
return render_to_response('facet_answer.html', dict(form=form), context_instance=RequestContext(request))

Resources