Django: Same form multiple times in one view - django

I have the three models User (django.contrib.auth), Screening and User_Screening. The User_Screening is a m2m table with the extra field status.
#models.py
from django.db import models
from django.contrib.auth.models import User
class Screening(models.Model):
title = models.CharField(max_length=255)
start = models.DateTimeField()
user_relation = models.ManyToManyField(User, blank=True,
through='User_Status')
class User_Status(models.Model):
ATTENDING = 'c'
NOT_ATTENDING = 'n'
PROJECTION = 'p'
STATUS_CHOICES = (
(ATTENDING, 'attending'),
(NOT_ATTENDING, 'not attending'),
(PROJECTING, 'projecting'),
)
screening = models.ForeignKey(Screening)
user = models.ForeignKey(User)
status = models.CharField(max_length=1, choices=STATUS_CHOICES)
Now I want to make a view, which shows all upcoming screenings. So far, so easy:
#views.py
#login_required()
def index(request):
current_screenings = Screening.objects.filter(start__gte=timezone.now())
context = {'current_screenings': current_screenings}
return render(request, 'schedule/index.html', context)
In this view, logged in users should be able, to update their status (from the User_Screening table). It could also be, that the user does not yet have a record for this screening, so one should be created.
I don't understand, how I could archive a form dropdown field for each screening, where the user can select his status. (Either ? if no status is set yet, attending, not attending or projection)
From what I understand I need multiple forms, that are aware what screening they are related to.
Also, Formsets seem not to work, because I can't always fill a form with initial data, as there could be records missing for some or all screenings. Furthermore I would not know, which form belongs to which of the screening objects.
Update:
What I want to end up with in HTML is something like this:
<form>
<h1>Current Screening 1</h1>
<select onchange="submit()" name="screening_user" id="s1">
<option value="att">Attending</option>
<option value="not_att">Not Attending</option>
<option selected="selected" value="pro">Projection</option>
</select>
<h1>Current Screening 2</h1>
<select onchange="submit()" name="screening_user" id="s2">
<!-- The 'Please Select' option is only visible, if the user does not
have a relation in 'User_Screening' for this screening -->
<option selected="selected" value="none">Please Select</option>
<option value="att">Attending</option>
<option value="not_att">Not Attending</option>
<option value="pro">Projection</option>
</select>
<!-- More Screenings -->
<h1>Current Screening n</h1>
<!-- select for screening n -->
</form>
Therefore a changing amount of forms is needed, from the same form with preloaded data according to the logged in user.

If a screening has a m2m relation to Users, than the attending users can be in that list. If not in attending... Well, than they are not attending! Does that make sense?
class Screening(models.Model):
title = models.CharField(max_length=255)
date = models.DateTimeField()
attending = models.ManyToManyField(User)
Form:
class ScreeningForm(ModelForm):
class Meta:
model = Screening
fieds = ['attending', ]
Formset:
ScreeningFormSet = modelformset_factory(Screenig, max_num=1)
formset = ScreeningFormSet(Screening=Screening.objects.filter(date__gte=now))

On one hand you could send the form data via an ajax request. In that request you would simply send one form and process the data. You would not need any formsets. Depending on your usecase this may add unnecessary traffic to your server.
Another solution would be to add another STATUS_CHOICE like 'nothing selected' as default value for the form that is used if there is no entry for the Screening User combination in the db. In the POST handler of your view you you can then just check if the form data is set to this value. In this case you simply ignore the form. If it is another value, then you set the db entry accordingly.

With some help from #django on feenode, I solved my problem. In the end, I stuck with formsets.
Considering the models.py from my question I had to change User_Status slightly, adding a NO_STATUS choice for the Select-Widget if no relation yet exist for the screening. Note that NO_STATUS is not a choice for the model.CharField!
#models.py
class User_Status(models.Model):
NO_STATUS = '?'
PROJECTIONIST = 'p'
ATTENDING = 'c'
NOT_ATTENDING = 'n'
STATUS_CHOICES = [
(ATTENDING, 'Anwesend'),
(NOT_ATTENDING, 'Nicht anwesend'),
(PROJECTIONIST, 'Vorführer'),
]
STATUS_CHOICES_AND_EMPTY = [(NO_STATUS, 'Please choose')] + STATUS_CHOICES
screening = models.ForeignKey(Screening)
user = models.ForeignKey(User)
status = models.CharField(max_length=1, choices=STATUS_CHOICES,
default=ATTENDING)
Next up, the form. The modified __init__ takes care, that 'Please choose' is only a valid choice, if that is set as the initial value for status. Otherwise, the choice is just not displayed.
#forms.py
class ScreeningUserStatusForm(forms.Form):
screening_id = forms.IntegerField(min_value=1)
status = forms.ChoiceField(choices=User_Status.STATUS_CHOICES_AND_EMPTY,
widget=forms.Select(attrs={"onChange":'submit()'}))
def __init__(self, *args, **kwargs):
super(ScreeningUserStatusForm, self).__init__(*args, **kwargs)
if self['status'].value() != User_Status.NO_STATUS:
#Once, a status is selected, the status should not be unset.
self.fields['status'].choices=User_Status.STATUS_CHOICES
Finally the view, that uses a formset to put all current screenings in it.
def update_user_status(screening, user, status):
#Get old status, if already exists.
new_status = User_Status.objects.get_or_create(screening=screening,
user=user)
# Add to selected status
new_status.status = status
new_status.save()
#login_required()
def index(request):
"""
displays all upcoming screenings
"""
# Get current screenings
current_screening_set = Screening.objects.filter(start__gte=timezone.now() - datetime.timedelta(hours=24)).order_by('start')
current_screening_list = current_screening_set.values('id')
ScreeningFormSet = formset_factory(ScreeningUserStatusForm, extra=0)
if request.method == 'POST':
#Get a formset bound to data from POST
formset = ScreeningFormSet(request.POST, request.FILES)
if formset.is_valid():
for form in formset.cleaned_data:
s = get_object_or_404(Screening, pk=form['screening_id'])
if form['status'] != User_Status.NO_STATUS:
update_user_status(screening=s, user=request.user, status=form['status'])
else:
#create a fresh formset
for form_data in current_screening_list:
screening = Screening.objects.get(pk=form_data['id'])
status = User_Status.objects.filter(user=request.user, screening=screening)
if status.count() != 1:
form_data['status'] = u'?'
else:
form_data['status'] = status.first().status
form_data['screening_id'] = form_data['id']
formset = ScreeningFormSet(initial=current_screening_list)
forms_and_curr_screenings = zip(formset.forms, current_screening_set)
context = {'formset' : formset, 'current_screenings' : forms_and_curr_screenings}
return render(request, 'schedule/index.html', context)
The formset.forms are zipped together with the current_screening_set, to provide additional data to each from. formset is additionally given to the template for the management_form.
A template could look like this
<!-- index.html -->
{% if current_screenings %}
<form method="post">
{{ formset.management_form }}
{% csrf_token %}
<table>
<thead>
<tr>
<th>Screening</th>
<th>My Status</th>
</tr>
</thead>
<tbody>
{% for form, screening in current_screenings %}
<tr>
<td>{{ screening }}</a></td>
<td>
{{ form.screening_id.as_hidden }}
{{ form.status }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</form>
{% endif %}

Related

Filter a Django form select element based on a previously selected element

Let's consider the following models
models.py
Class Brand(models.Model):
company_name = models.CharField(max_length=100)
class CarModel(models.Model):
brand = models.ForeignKey(Brand)
name = models.CharField(max_length=100)
Class FleetCars(models.Model):
model_car = models.Foreignkey(CarModel)
What is the best way to solve this problem in django?
Suppose a form (for insertions in FleetCars) consists of two select elements, like this:
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<br />Brand:
<select>
<option value="Brand1">Brand1</option>
<option value="Brand2">Brand2</option>
</select>
<br />
<br />Model:
<select>
<option value="Model1_B1">Model1_B1</option>
<option value="Model1_B2">Model1_B2</option>
</select>
</body>
</html>
In this case, I want the options in the second select to depend on the value selected in the first. For example, if the user chose Brand1 for a Brand in the first select, the second select would be filtered with only cars whose Brand was Brand1, that is, only "Model1_B1".
Obs.
I saw many solutions with forms.ModelChoiceField, but only works with edit and since the user do not change the brand.
After hours and hours of research, without success, I decided to try to solve on my own. The solution that I found maybe don't be the best or the more elegant, but is working. (For download full Django project, click on this repo => https://github.com/Sidon/djfkf/.)
models.py
from django.db import models
class Brand(models.Model):
company_name = models.CharField(max_length=100)
def __str__(self):
return self.company_name
class Car(models.Model):
brand = models.ForeignKey(Brand)
name = models.CharField(max_length=100)
def brand_name(self):
return self.brand.company_name
def __str__(self):
return self.name
class Fleet(models.Model):
car = models.ForeignKey(Car)
description = models.CharField(max_length=100)
def car_name(self):
return self.car.name
def brand(self):
return self.car.brand.company_name
def __str__(self):
return self.description
The goal is to register cars on the fleet. The only fields that are will be recorded: Car (foreign key) and description. On the form, there will be one select element for brands that will work just only as a helper for to filter the car's combo box.
forms.py
import json
from django import forms
from .models import *
class RegCarForm(forms.ModelForm):
dcars = {}
list_cars = []
for car in Car.objects.all():
if car.brand.company_name in dcars:
dcars[car.brand.company_name].append(car.name)
else:
dcars[car.brand.company_name] = [car.name]
list_cars.append((car.name,car.name))
brands = [str(brand) for brand in Brand.objects.all()]
brand_select = forms.ChoiceField(choices=([(brand, brand) for brand in brands]))
car_select = forms.ChoiceField(choices=(list_cars))
brands = json.dumps(brands)
cars = json.dumps(dcars)
class Meta:
model = Fleet
fields = ('brand_select', 'car_select', 'description',)
RegCarForm is a form for register cars, there are three fields: brand_select, car_select, and description. In addition, I defined two JSON attributes: 1) a dictionary whose keys are brands (strings) and values are lists of respective's cars and 2) A list of strings that represent the brands. Those two attributes will work as helpers for JS functions.
views.py
from django.shortcuts import render
from .forms import RegCarForm
from .models import *
def regcar(request):
if request.method == 'POST':
car_form = RegCarForm(data=request.POST)
if car_form.is_valid():
cdata = car_form.cleaned_data.get
car_selected = Car.objects.filter(name=cdata('car_select'))
reg1 = Fleet(car_id=car_selected[0].id, description=cdata('description'))
reg1.save()
else:
print ('Invalid')
else:
car_form = RegCarForm()
return render(request, 'core/regcar.html', {'car_form': car_form})
The view is practically auto-explanatory. Assigns the Form to the car_form variable, render the template core/regcar.html and, after Post, make the validation of the form and save the data.
regcar.html (template django)
{% extends "base.html" %}
{% block head %}
{% endblock %}
{% block content %}
<h1>Registering cars on the fleet. <br />(Populate one drop down based on selection in another)</h1>
<p>Change the contents of drop down Car based on the selection in dropdown Brand, using Django-forms + Javascritp</p>
<div class="select-style">
<form action="." method="post">
{% csrf_token %}
{{ car_form.as_p }}
<p><input type="submit" value="Register a car"></p>
</form>
</div>
{% endblock %}
{% block js %}
{% include "js1.html" %}
{% endblock %}
The template only just renders the form and load the script JS. Nothing else.
Finally, the js script, that makes the hard work.
{% block js %}
<script language="javascript">
$('#id_brand_select').change(function() {populateCar(this)});
$('#id_description').addClass('descriptions');
cars = {{ car_form.cars | safe }}
brands = {{ car_form.brands | safe}};
populateBrand();
$("#id_car_select").empty();
$("#id_car_select").append('<option value="" disabled selected>First select a brand</option>');
function populateBrand() {
$('#id_brand_select').empty();
$("#id_brand_select").append('<option value="" disabled selected>Select your option</option>');
$.each(brands, function(v) {
$('#id_brand_select')
.append($("<option></option>")
.attr("value", brands[v])
.text(brands[v]));
});
}
function populateCar(event) {
brand = $("#id_brand_select option:selected").text();
$("#id_car_select").empty();
$("#id_car_select").append('<option value="" disabled selected>Select your option</option>');
for (let [b, bcars] of Object.entries(cars)) {
if (b == brand) {
//alert(b);
for (car in bcars) {
$('#id_car_select')
.append($("<option></option>")
.attr("value", bcars[car])
.text(bcars[car]));
}
}
}
}
</script>
{% endblock %}
When the document is loaded, this script assigns the change event of brand_select (combo for selection of brand) to the function poplulateCar, assign the form's JASON attributes (cars and brands) to a JS variables and call the populateBrand function.
Links:
Full project in Django:
https://github.com/Sidon/djfkf/
class Country(models.Model):
country_name=models.CharField(max_length=10, blank=True, null=True)
class State(models.Model):
state_name=models.CharField(max_length=10, blank=True, null=True)
class MyCustomModal(models.Model):
country = models.ForeignKey(Country, on_delete=models.CASCADE, null=True, blank=True)
state = models.ForeignKey(State, on_delete=models.CASCADE, null=True, blank=True)
Here is my Form
class MyCustomForm(forms.ModelForm):
class Meta:
model = MyCustomModal
fields = [
'country',
'state',
]
def __init__(self, *args, **kwargs):
super(MyCustomForm, self).__init__(*args, **kwargs)
self.fields['country'] = forms.ChoiceField(choices=[('1','india'),('2','US')])
self.fields['state'].queryset = State.objects.filter(pk=2)

Form fields missing in Django, just button visable

New to Django and having problem seeing form fields displayed. What I see is just the submit button. If pressed, the form is finally presented, but with the format for a form that had bad data (typical 'this field is required' error for each box, red box, etc).
The form works fine after entering data and again pressing submit (stores entries in my db). I have a number of forms on the same page that have the same behavior.
Example of one form:
#model
class dbPara(models.Model): #parameters
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
username = models.CharField(max_length=10)
turns = models.FloatField(default=27)
units = models.FloatField(default=5)
rise = models.FloatField(default=2.9)
rescutL = models.FloatField(default=0.0833333333)
rescutH = models.FloatField(default=0.333333333)
LorR = models.CharField(max_length=1, default='R')
def __str__(self):
return self.timestamp, self.username, self.turns, self.units, self.rise, self.rescutL, self.rescutH, self.LorR
#form
class ParaForm(ModelForm):
class Meta:
model = dbPara
widgets = {'username': forms.HiddenInput()}
fields =['username', 'turns', 'units', 'rise', 'rescutL', 'rescutH', 'LorR']
#view
def importParameters(request):
if request.method == 'GET':
form = ParaForm()
else:
form = ParaForm(request.POST)
if form.is_valid():
entry=dbPara(username = request.POST.get('username'),
turns = request.POST.get('turns'),
units = request.POST.get('units'),
rise = request.POST.get('rise'),
rescutL = request.POST.get('rescutL'),
rescutH = request.POST.get('rescutH'),
LorR = request.POST.get('LorR')
)
entry.save()
return render(request, 'main.html',
{'ParaHTML' : form })
#url
urlpatterns = patterns('Inputs.views',
url(r'^importParameters/$', 'importParameters', name='urlParameters'),
)
#main.html
<div class='col-lg-3'>
<h4>Set Rosetta Parameters</h4>
<action="{% url "urlParameters" %}" method="post">{% csrf_token %}
{{ ParaHTML|crispy }}
<input type="hidden" name = "username" value = "{{ user.get_username }}">
<input type="submit" class="btn btn-primary" value="Set">
</form>
</div>
Appreciate any advice (better simple than 'most correct but complicated')
Could it be due to using default in the model? Would that not 'fill in the form' and result in 'POST' at the initial visit to the page, resulting in just the button? Thoughts?
One Suggesestion here ....
if Using request.POST.get('anything') simply then it Will raise error if particular string not find as in example('anything') string...
Because request.POST.get('anything') will return None if 'anything' is not in request.POST.
Additionally, .get allows you to provide an additional parameter of a default value which is returned if the key is not in the dictionary.
e.g: Corrected will be request.POST.get('anything', 'mydefaultvalue')

Cannot get selection from one page to another - need to know what choice user chose

I'm trying to let the user select one 'thing' from a list (from the database), then go find other stuff in the database using that record. But I cannot get the selection info from the selection page.
I'll try to make this a pretty complete snapshot of the relevant code, but I may remove too much or leave too much in, sorry.
my models.py:
urlpatterns = patterns('',
url(r'^$', 'dblook.views.index', name='home'),
url(r'^dblook3/', 'dblook.views.try3', name='home2'),
url(r'^dblook4/', 'dblook.views.try4', name='home3'),
)
my dblook/models.py:
from django.db import models
class serial_number(models.Model):
def __unicode__(self):
return self.serialno
#return self.question
class Meta:
managed=False
db_table='serial_number'
sn_id = models.AutoField(primary_key=True)
serialno = models.CharField(max_length=128)
comment = models.ForeignKey(comment,null=True,db_column='comment')
my views.py (I will skip all the imports other than the database model import. If anyone really wants them I'll update with them)
from dblook.models import *
class SerialnoSelectForm(forms.Form):
serialno = forms.CharField(max_length=16)
selected = forms.BooleanField()
class serialform(ModelForm):
class Meta:
model = serial_number
exclude=('comment','sn_id')
selected = forms.BooleanField()
class snselect(forms.Form):
sno = forms.ChoiceField()
def try3(request):
if ( request.POST ):
output = "HEllo world, thanks for posting"
return HttpResponse(output)
else:
sslst = snselect(serial_number.objects.filter(serialno__startswith="A128").order_by('-serialno'))
t = loader.get_template('select_serialno.html')
c = Context({
'sslst': sslst,
})
c.update(csrf(request))
return HttpResponse(t.render(c))
def try4(request,dsn):
if ( request.POST ):
output = "HEllo world, thanks for posting to 4"
return HttpResponse(output)
else:
return HttpResponse("Error")
And my template (select_serialno.html) is:
<h1>Select a serial number</h1>
<ul>
<form method='post' action'='/dbtest4/{{serial_number.sn_id}}/showme'>
{% csrf_token %}
{% for sn in sslst %}
<input type="submit" name="sn.serialno" id="choice{{ forloop.counter }}" value="{{choice.id}}"/>
<label for="choice{{ forloop.counter }}">{{ sn.serialno }}</label><br/>
{% endfor %}
<input type="submit" value="data" />
</form>
When I go to dblook3, I get a nice list from the database of serial numbers, along with a button that, if I hit goes immediately to the dblook4 URL (in this case, its ALWAYS '/dbtest4//showme/' instead of something like '/dbtest4/3/showme/). Unfortunately, I cannot seem to have any way to tell what button they hit.
No matter WHAT I put in for the 'stuff' in <form method='post' action'='/dbtest/{{stuff}}/showme'>, it is always empty.
I also tried things like if( 'choice' in request.POST ): in try4 in veiws.py, but that didn't work either.
So, how do I get ANY information about what was selected from 'look3' over to 'look4'? I'll take just about anything... However, if you can explain why I'm doing that hopefully your answer will not only solve my problem, but help others understand...
(if the above looks pretty 'evolutionary' that's because I've been hacking on this for 3 days now...)
Thanks!
You need to POST the information to the look4 dblook form:
<form method='post' action'='{% url dblook.views.try4 %}'>
At the moment you have /dbtest/{{serial_number.sn_id}}/showme which doesn't make any sense. You don't have a serial_number variable in your context so I don't know where that comes from. You have def try4(request,dsn): as your view definition which suggests that you are trying to load information on the try4 view depending on what was selected fromt he try3 view (although I am guessing this as you haven't explained what you are trying to do). If that is the case, you need to do that based on the data passed via POST instead of url parameters. Something very vaguely like the following:
def try4(request):
if request.method == "POST":
form = snselect(request.POST)
if form.is_valid():
data = form.cleaned_data
# Get the selected item from your choice field and retrieve the
# corresonding model object with that id
...

Django- populating an "update/edit" form with an autofield and foreignkey

I'm new to Django and I'm creating an app to create and display employee data for my company.
Currently the model, new employee form, employee table display, login/logout, all works. I am working on editing the current listings.
I have hover on row links to pass the pk (employeeid) over the url and the form is populating correctly- except the manytomanyfields are not populating, and the pk is incrementing, resulting in a duplicate entry (other than any data changes made).
I will only put in sample of the code because the model/form has 35 total fields which makes for very long code the way i did the form fields manually (to achieve a prettier format).
#view.py #SEE EDIT BELOW FOR CORRECT METHOD
#login_required
def employee_details(request, empid): #empid passed through URL/link
obj_list = Employee.objects.all()
e = Employee.objects.filter(pk=int(empid)).values()[0]
form = EmployeeForm(e)
context_instance=RequestContext(request) #I seem to always need this for {%extend "base.html" %} to work correctly
return render_to_response('employee_create.html', locals(), context_instance,)
#URLconf
(r'^employee/(?P<empid>\d+)/$', employee_details),
# snippets of employee_create.html. The same template used for create and update/edit, may be a source of problems, they do have different views- just render to same template to stay DRY, but could add an additional layer of extend for differences needed between the new and edit requests EDIT: added a 3rd layer of templates to solve this "problem". not shown in code here- easy enough to add another child template
{% extends "base.html" %}
{% block title %}New Entry{% endblock %}
{% block content %}
<div id="employeeform">
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<form action="/newemp/" method="post" class="employeeform">{% csrf_token %} #SEE EDIT
<div class="left_field">
{{ form.employeeid.value }}
{{ form.currentemployee.errors }}
<label for="currentemployee" >Current Employee?</label>
{{ form.currentemployee }}<br/><br/>
{{ form.employer.errors }}
<label for="employer" class="fixedwidth">Employer:</label>
{{ form.employer }}<br/>
{{ form.last_name.errors }}
<label for="last_name" class="fixedwidth">Last Name:</label>
{{ form.last_name }}<br/>
{{ form.facility.errors }} #ManyToMany
<label for="facility" class="fixedwidth">Facility:</label>
{{ form.facility }}<br/><br/>
</div>
<div id="submit"><br/>
<input type="submit" value="Submit">
</div>
</form>
#models.py
class Employee(models.Model):
employeeid = models.AutoField(primary_key=True, verbose_name='Employee ID #')
currentemployee = models.BooleanField(null=False, blank=True, verbose_name='Current Employee?')
employer = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
facility = models.ForeignKey(Facility, null=True, blank=True)
base.html just has a header on top, a menu on the left and a big empty div where the forms, employee tables, etc all extend into.
screenshot2
So, how do I need to change my view and/or the in the template to update an entry, rather than creating a new one? (
And how do I populate the correct foriegnkeys? (the drop down boxes have the right options available, but the "-----" is selected even though the original database entry contains the right information.
Let me know if i need to include some more files/code
I have more pics too but i cant link more or insert them as a new user :< I'll just have to contribute and help out other people! :D
EDIT:
I've been working on this more and haven't gotten too far. I still can't get the drop-down fields to select the values saved in the database (SQLite3).
But the main issue I'm trying to figure out is how to save as an update, rather than a new entry. save(force_update=True) is not working with the default ModelForm save parameters.
views.py
def employee_details(request, empid):
context_instance=RequestContext(request)
obj_list = Employee.objects.all()
if request.method == 'POST':
e = Employee.objects.get(pk=int(empid))
form = EmployeeForm(request.POST, instance=e)
if form.is_valid():
form.save()
return HttpResponseRedirect('/emp_submited/')
else:
e = Employee.objects.get(pk=int(empid))
form = EmployeeForm(instance=e)
return render_to_response('employee_details.html', {'form': form}, context_instance,)
also changed template form action to "" (from /newemp/ which was the correct location for my new employee tempalte, but not the update.
Thanks to this similar question.
updating a form in djnago is simple:
steps:
1. extract the previous data of the form and populate the edit form with this these details to show to user
2. get the new data from the edit form and store it into the database
step1:
getting the previous data
views.py
def edit_user_post(request, topic_id):
if request.method == 'POST':
form = UserPostForm(request.POST)
if form.is_valid():
#let user here be foreign key for the PostTopicModel
user = User.objects.get(username = request.user.username)
#now set the user for the form like: user = user
#get the other form values and post them
#eg:topic_heading = form.cleaned_data('topic_heading')
#save the details into db
#redirect
else:
#get the current post details
post_details = UserPostModel.objcets.get(id = topic_id)
data = {'topic_heading':topic.topic_heading,'topic_detail':topic.topic_detail,'topic_link':topic.topic_link,'tags':topic.tags}
#populate the edit form with previous details:
form = UserPostForm(initial = data)
return render(request,'link_to_template',{'form':form})

Django form not saving default image name

I've got a form which includes the option to upload an image. In my model, I've defined a default image name to use when no image is selected for upload. When selecting a file, the form uploads the file to my media directory and properly places the filename in the db field (working as it should). When not selecting a file, that field is left blank in the db. When adding an item to that same db table using Django Admin, the default filename is correctly placed in the db field when no image is selected (and works properly when an image is selected). It's only when using the form and not selecting an image does it not work properly. I've look around for a while but have yet to come up with anything that could help. Any ideas? Any help is much appreciated.
models.py
class Beer(models.Model):
beername = models.CharField(max_length=150)
brewer = models.ForeignKey(Brewery)
style = models.ForeignKey(BeerStyle)
abv = models.DecimalField(max_digits=4, decimal_places=2)
beerdescription = models.TextField()
picture = models.ImageField(upload_to='site_media/pictures/',
default='pictures/no_beer_picture.jpg')
def __unicode__(self):
return self.beername
forms.py
class BeerAddForm(forms.Form):
beername = forms.CharField(
label=u'Name',
widget=forms.TextInput(attrs={'size': 75})
)
style = forms.ModelChoiceField(
BeerStyle.objects.all(),
label=u'Style',
widget=forms.Select()
)
abv = forms.DecimalField(
label=u'ABV',
widget=forms.TextInput(attrs={'size': 8})
)
beerdescription = forms.CharField(
label=u'Description',
widget=forms.Textarea
)
picture = forms.ImageField(
required=False,
label=u'Picture',
widget=forms.FileInput,
initial='pictures/no_beer_picture.jpg'
)
views.py
def beeradd(request, brewery_id):
brewery = get_object_or_404(Brewery, id=brewery_id)
if request.method == 'POST':
form = BeerAddForm(request.POST, request.FILES)
if form.is_valid():
# Create or get beer
beer = Beer.objects.create(
beername = form.cleaned_data['beername'],
brewer = brewery,
style = form.cleaned_data['style'],
abv = form.cleaned_data['abv'],
beerdescription = form.cleaned_data['beerdescription'],
picture = form.cleaned_data['picture']
)
return HttpResponseRedirect('/beers/')
else:
form = BeerAddForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response('beer_add.html', variables)
beer_add.html (the form in question)
{% extends "base.html" %}
{% block title %}Add a Beer{% endblock %}
{% block head %}Add a Beer{% endblock %}
{% block content %}
<form enctype="multipart/form-data" method="post" action=".">
{{ form.as_p }}
<input type="submit" value="save" />
</form>
{% endblock %}
I would set the default in the view code, after the user submitted the form. So take the initial argument for picture out of the form definition and do something like this in your view:
def beeradd(request, brewery_id):
brewery = get_object_or_404(Brewery, id=brewery_id)
if request.method == 'POST':
form = BeerAddForm(request.POST, request.FILES)
if form.is_valid():
# Create or get beer
pic = form.cleaned_data['picture']
if not pic:
pic = 'pictures/no_beer_picture.jpg'
beer = Beer.objects.create(
beername = form.cleaned_data['beername'],
brewer = brewery,
style = form.cleaned_data['style'],
abv = form.cleaned_data['abv'],
beerdescription = form.cleaned_data['beerdescription'],
picture = pic
)
...
I think the problem that you are seeing is that the initial may populate the file field with that value, but when the form gets submitted the value 'pictures/no_beer_picture.jpg' is not a valid file on the user's computer so no file is sent with the form. You can verify what is getting sent by printing out form.cleaned_data['picture'] before trying to save the model.
You may want to check to see if you can just assign a string value to the picture attribute on Beer or if you actually need to assign a file.