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.
Related
I have an app that takes as one of the form fields a multiple listbox. The data is defined on the html page as "temp_groups", then in the view.py the form.cleaned_data['actual field'] is set to the extracted temp field. The data extracted appears correct (2,3) and looking at the same field after the form.save() is executed looks correct. But the data is not rendered to the database that way. It is using the value set on the actual "hidden" field on the html page. Here is the code
Tried using the actual field name (group_members) but the result is only the last value selected from the list is actually posted.
Also tried setting up a widget on the forms.py file where the data used in supplying comes from the query of the User object (auth_user). But this had no impact.
url.py
urlpatterns = [..
path('newgroups/', views.newgroups, name='newgroups'),
forms.py
class QueryGroupsForm(forms.ModelForm):
class Meta:
model = QueryGroups
fields = ['group_name',
'group_desc',
'group_members',
]
models.py
class QueryGroups(models.Model):
group_name = models.CharField(max_length=40, null=True, blank=True)
group_desc = models.CharField(max_length=100, null=True, blank=True)
group_members = models.CharField(max_length=100, null=True, blank=True)
querygroups.html
<div class="col-3">
<div class="form-group">
<label for="temp_members" class="" style="font-weight: bold;">Group Members<label>
<select id="temp_members" name="temp_members" class="form-control" multiple="multiple">
{% for nextUser in users %}
<option value="{{nextUser.id}}">{{nextUser.username}}</option>
{% endfor %}
</select>
<input type="hidden" name="group_members" id="group_members" value="TBD">
</div>
</div>
views.py
def newgroups(request):
if request.method == "POST":
group_form = QueryGroupsForm(request.POST)
user_list = request.POST.getlist('temp_members')
users_of_group = ""
for next_user in user_list:
users_of_groups += next_user + ","
list_len = len(users_of_groups)
users_of_group = users_of_group[:list_len - 1]
if group_form.is_valid():
group_form.cleaned_data['group_members'] = str(user_of_group)
group_form.save()
post_list = group_form.cleaned_data['group_members']
return HttpResponse("user_of_group: " + users_of_group + " post_list: " + str(post_list))
What is presented on the HttpResponse is "users_of_group: 2,3 post_list: 2,3". However, what is in the group_members field in the database is the value "TBD" which is set as the hidden field in the html file.
I'm a django newbie so a verbose answer will be greatly appreciated. I'm enforcing a capacity limit on any newly created Bottle objects in my model, like so:
class Bottle(models.Model):
name = models.CharField(max_length=150, blank=False, default="")
brand = models.ForeignKey(Brand, on_delete=models.CASCADE, related_name="bottles")
vintage = models.IntegerField('vintage', choices=YEAR_CHOICES, default=datetime.datetime.now().year)
capacity = models.IntegerField(default=750,
validators=[MaxValueValidator(2000, message="Must be less than 2000")
,MinValueValidator(50, message="Must be more than 50")])
My BottleForm looks like so:
class BottleForm(ModelForm):
class Meta:
model = Bottle
fields = '__all__'
My view (with form validation logic based on this answer):
def index(request):
args = {}
user = request.user
object = Bottle.objects.filter(brand__business__owner_id=user.id).all(). \
values('brand__name', 'name', 'capacity', 'vintage').annotate(Count('brand')).order_by('brand__count')
args['object'] = object
if request.method == "POST":
form = BottleForm(request.POST)
if form.is_valid():
bottle = form.save(commit=False)
bottle.save()
return redirect('index')
else:
form = BottleForm()
args['form'] = form
return render(request, template_name="index.pug", context=args)
And my template (in pug format), like so:
form(class="form-horizontal")(method="post" action=".")
| {% csrf_token %}
for field in da_form
div(class="form-group")
label(class="col-lg-3 col-md-3 col-sm-3 control-label") {{field.label_tag}}
div(class="col-lg-9 col-md-9 col-sm-9")
| {{ field|add_class:"form-control" }}
input(class="btn btn-primary")(type="submit" value="submit")
After a few hours of messing with my code and browsing SO, I managed to display the error by adding {{ form.errors }} to my template, but that only shows after the page has already been reloaded and in a very ugly form: see here.
What I'd like is to utilize django's built-in popover error messages without reloading page (see example on default non-empty field), which is so much better from a UX standpoint.
That is not a Django message. That is an HTML5 validation message, which is enforced directly by your browser. Django simply outputs the input field as type number with a max attribute:
<input type="number" name="capacity" max="750">
I'm not sure if your (horrible) pug templating thing is getting in the way, or whether it's just that Django doesn't pass on these arguments when you use validators. You may need to redefine the field in the form, specifying the max and min values:
class BottleForm(ModelForm):
capacity = forms.IntegerField(initial=750, max_value=2000, min_value=250)
(Note, doing {{ field.errors }} alongside each field gives a much better display than just doing {{ form.errors }} at the top, anyway.)
Here's the goal: when the user submits the form, use one view to send the submitted data to the database, then redirect back to the form, but with the data pre-populated. This is mostly working, but something about my implementation is wrapping extra quotes around the string. For now, I'm just using a super-simple form, btw. I enter Billy, and the pre-pop is: "Billy", if I click submit again, it comes back as: "\"Billy\"", then "\"\\\"Billy\\\"\"", and so on (as far as I have tested, anyways.
relevant views are:
def editUsers(request):
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = usersForm(request.POST)
# check whether it's valid:
# process the data in form.cleaned_data as required
# redirect to a new URL:
name = json.dumps(form.data['user_name'])
request.session['editUserName'] = name
# call out to limboLogic.py to update values
test = name
return redirect('../users')
# if a GET (or any other method) we'll create a blank form
else:
return redirect('../users')
from .forms import *
def users(request):
form = None
if 'editUserName' not in request.session:
# create a blank form
form = usersForm()
else:
# form = equipmentForm(initial='jim') - used to make sure I was branching the if/else correctly
form = usersForm(initial={'user_name':request.session['editUserName']}, auto_id=False) #limboLogic.GetUserInfo(name))
return render(request, 'limboHtml/UserManagement.html', {'form': form})
form is simply:
class usersForm(forms.Form):
user_name = forms.CharField(label='New User\'s name', max_length=100)
and the template is:
{% extends "base.html" %}
{% block content %}
<div class="row">
<p>This is the user management page</p><br>
<form action="/edit/users.html" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="OK">
</form>
<br><p class="bold">This is below the form</p>
</div>
{% endblock %}
thoughts?
I can't quite say what the intracies are here, but the problem involves the fact that I was using a json class. I used this site as a guide and managed to fix the problem. note that the key aspect is inside the second if:
name = form.cleaned_data['user_name'] works fine,
name = json.dumps(form.data['user_name']) does not
the whole function as it now stands:
def editUsers(request):
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = usersForm(request.POST)
# check whether it's valid:
# process the data in form.cleaned_data as required
# redirect to a new URL:
if form.is_valid():
name = form.cleaned_data['user_name']
# name = json.dumps(form.data['user_name'])
request.session['editUserName'] = name
# call out to limboLogic.py to update values
test = name
return redirect('../users')
# if a GET (or any other method) we'll create a blank form
return redirect('../users')
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')
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})