I have an edit function that I want the user to be able to edit the Picture object (tags), while keeping the old image. The form is looking for a photo but I do want the user to be able to change the image - just the other information.
How do you pass the original image data from the picture object into the PictureForm so it validates?
My view:
#csrf_protect
#login_required
def edit_picture(request, picture_id, template_name="picture/newuserpicture.html"):
picture = get_object_or_404(Picture, id=picture_id)
if request.user != picture.user:
return HttpResponseForbidden()
if request.method == 'POST':
form = PictureForm(request.POST or None, request.FILES or None, instance=picture)
if form.is_valid():
form.save()
return HttpResponseRedirect('/picture/%d/' % picture.id )
else:
form = PictureForm(instance=picture)
data = { "picture":picture, "form":form }
return render_to_response(template_name,
data,
context_instance=RequestContext(request))
I think this thread should give you a clue how to make existing fields readonly:
In a Django form, how do I make a field readonly (or disabled) so that it cannot be edited?
I you want to hide the picture completely and stumble across validation errors because the field is marked as required in your model definition (blank=True) another option would be to override the form's save method and tweak the field's required attribute.
Something along these lines:
def __init__(self, *args, **kwargs):
super(PictureForm, self).__init__(*args, **kwargs)
for key in self.fields:
self.fields[key].required = False
I guess I am not sure how to just comment on the original question, but what do you mean by validate? If you are just needing to ensure that the picture object's picture is actually the same after the form is done can you not make some custom clean methods for the form? in a clean method you could compare all metadata and the image then proceed to the forms save.
Related
I apologize for my confusing title but I hope the code explains it better.
In my views.py file I have the follow view
def create_view(request):
context = {}
form = CreateForm(request.POST)
if request.method == "POST":
if form.is_valid():
instance = form.save(commit=False)
instance.author = request.user
instance.save()
instance.author.profile.participating_in = Post.objects.get(
title=instance.title
)
instance.save()
print(instance.author.profile.participating_in)
context["form"] = form
return render(request, "post/post_form.html", context)
when I print out the value of instance.author.profile.participating_in it shows up in my terminal however when I check the admin page it doesnt update at all. I'm sure I messed up somewhere silly but I cant seem to find it. Thanks!
participating_in is the profile model field, but you are not calling the save() method for profile anywhere.
You have to do it like the following:
profile = instance.author.profile
profile.participating_in = Post.objects.get(title=instance.title)
profile.save()
If participating_in is ManyToManyField then we can do it like this:
post = Post.objects.get(title=instance.title)
instance.author.profile.participating_in.add(post)
Note that add(), create(), remove(), clear(), and set() all
apply database changes immediately for all types of related fields. In
other words, there is no need to call save() on either end of the
relationship.
Look at Related objects reference
I am trying to set a field value when a form is initiated.
The value of this field is retrieved when we enter the view - the view being the timesheet. Then for each Time set in the view, I want to relate it back to the timesheet.
#login_required
#requires_csrf_token
def timesheet(request, timesheet_id):
timesheet = TimeSheet.objects.get(pk=timesheet_id)
NewTimeFormSet = modelformset_factory(Time, form=TimeForm, formset=RequiredFormSet)
if request.method == 'POST':
newtime_formset = NewTimeFormSet(request.POST, request.FILES)
for form in newtime_formset:
if form.is_valid():
form.save()
#then render template etc
So, to make sure the form validates I want to set this field when the form is initiated. When I try to set this field after POST in the view, I haven't been able to get the field to set or form to validate.
My code gets the timesheet_id when the model instance is initiated on entering the view
def __init__(self, *args, **kwargs):
# this allows it to get the timesheet_id
print "initiating a timesheet"
super(TimeSheet, self).__init__(*args, **kwargs)
And then the form is generated and I run the form init. So this is what I've tried
class TimeForm(forms.ModelForm):
class Meta:
model = Time
fields = ['project_id', 'date_worked', 'hours', 'description', 'timesheet_id',]
# some labels and widgets, the timesheet_id has a hidden input
def __init__(self, *args, **kwargs):
print "initiating form"
super(TimeForm, self).__init__(*args, **kwargs)
timesheet = TimeSheet.objects.get(id=timesheet_id)
self.fields['timesheet_id'] = timesheet
This raises the error
NameError: global name 'timesheet_id' is not defined
I don't know how to do this...
I've also attempted setting the field in the form clean() method, but it populates (shown by a print) and then still doesn't validate and I raise a formset error 'This field is required'.
Help!
You don't actually accept a timesheet_id parameter in the form init method, so that value is not defined hence the error.
However, this is the wrong approach. There is no point passing a value to a form, outputting it as a hidden field, then getting it back, when you had it all along. The way to do this is to exclude the value from the form's fields, and set it on save.
class TimeForm(forms.ModelForm):
class Meta:
model = Time
fields = ['project_id', 'date_worked', 'hours', 'description',]
...
if request.method == 'POST':
newtime_formset = NewTimeFormSet(request.POST, request.FILES)
if newtime_formset.is_valid():
for form in newtime_formset:
new_time = form.save(commit=False)
new_time.timesheet_id = 1 # or whatever
new_time.save()
Note, again, you should check the validity of the whole formset before iterating through to save; otherwise you might end up saving some of them before encountering an invalid form.
So, I'm using Django's Model Formset to produce sets of forms for different data. It is working great, but I want to add a feature where when a user displays the formset and, say, updates 2 out of the 10 items, I can track just the 2 updated, and output a message like "You have updated 2 items" kind-of-thing.
Do Django Model Formsets have a built in API for this? I can't seem to find it on the Django Docs.
I've tried various approaches but keep getting this when using the code offered by Peter below:
'Attendance' object has no attribute 'has_changed.'
If I switch form.has_changed to formset.has_changed(), I get
'list' object has no attribute 'has_changed'
My View and Post method
class AttendanceView(TemplateView):
template_name = 'example.html'
def changed_forms(self, formset):
return sum(1 for form in formset if form.has_changed())
def post(self, request, *args, **kwargs):
formset = AttendanceFormSet(request.POST)
if formset.is_valid():
formset = formset.save()
forms_changed = self.changed_forms(formset)
context = self.get_context_data(**kwargs)
context['total_changed_forms'] = forms_changed
return self.render_to_response(context)
else:
return HttpResponse("POST failed")
So I figured it out, just change:
formset = formset.save()
to
formset.save()
Formsets have a has_changed method which will report whether or not any of its forms have been changed. That's not exactly what you're looking for, but if you look at its implementation it should show you how to do it. That method is:
def has_changed(self):
"""
Returns true if data in any form differs from initial.
"""
return any(form.has_changed() for form in self)
So you can count changed forms with:
def changed_forms(formset):
return sum(1 for form in formset if form.has_changed())
Or if you're comfortable using the integer meanings of boolean values:
return sum(form.has_changed() for form in formset)
I personally find that unappealing compared to the more explicit mapping from true to 1, but opinions differ there.
So I have this custom ModelForm that I created that takes in a variable creator_list for the queryset like this:
class UserModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.get_full_name()
class OrderCreateForm(ModelForm):
class Meta:
model=Order
fields=('work_type', 'comment',)
def __init__(self, creator_list=None, *args, **kwargs):
super(OrderCreateForm, self).__init__(*args, **kwargs)
if creator_list:
self.fields['creator'] = UserModelChoiceField(
queryset=creator_list,
empty_label="Select a user",
widget=Select(attrs={
'onchange': "Dajaxice.doors.orders_create_creator_changed(fill_other_fields, {'creator_pk': this.options[this.selectedIndex].value})"
})
)
self.fields['place'] = UserModelChoiceField(
queryset=User.objects.none(),
empty_label="Select a creator first"
)
When I am simply displaying the fields, everything works perfectly. However during a POST submission. I get errors that I don't know how to debug.
My views.py looks like this:
user = request.user
dictionary = get_order_create_dictionary(user)
if request.method == 'POST':
#import ipdb; ipdb.set_trace()
form = OrderCreateForm(request.POST)
if form.is_valid():
creator = form.cleaned_data['creator']
place = form.cleaned_data['place']
work_type = form.cleaned_data['work_type']
comment = form.cleaned_data['comment']
new_order = Order.objects.create(
creator =creator,
place =place,
work_type=work_type,
comment =comment
)
messages.success(request, "Your order #{} had been created!".format(new_order.pk))
logger.info("{user} created order #{pk}".format(user=user, pk=new_order.pk))
return HttpResponseRedirect(reverse('orders_detail', kwargs={'pk': new_order.pk}))
else:
return render(request, 'doors/orders/create.html', {'form': form, 'can_assign_creator': dictionary['can_assign_creator']})
else:
if dictionary:
return render(request, 'doors/orders/create.html', {
'form': OrderCreateForm(creator_list=dictionary['creator_list']),
'can_assign_creator': dictionary['can_assign_creator']
})
else:
return HttpResponseRedirect(reverse('orders_list'))
get_order_create_dictionary() simply returns a dictionary that looks like this:
dictionary = {
'creator_list': Order.objects.all(), # Or some kind of filtered Order.
'can_assign_order: 1, # Or 0. This variable is used in the template to control access to what gets displayed.
}
Currently with the above code I get an error like this when I try to POST something:
AttributeError: 'QueryDict' object has no attribute 'all'
on the line "return render(request, 'doors/orders/create.html', {'form': form, 'can_assign_creator': dictionary['can_assign_creator']})"
I thought it has something to do with the line form = OrderCreateForm(request.POST) so I changed that to form = OrderCreateForm(request.POST, creator_list=dictionary['creator_list']). But then I get this error:
TypeError: __init__() got multiple values for keyword argument 'creator_list'
on the line "form = OrderCreateForm(request.POST, creator_list=dictionary['creator_list'])"
I have no clue how to resolve this. I appreciate any help or tips! Thanks!
EDIT:
I changed the line to form = OrderCreateForm(dictionary['creator_list'], request.POST) and now the validation works, but it won't let me submit a valid POST. It keeps saying Select a valid choice. That choice is not one of the available choices. for the place. This probably has something to do with how I populate the <option> with place using Ajax depending on what the creator is.
You'd better instantiate Form instances with only named arguments, i.e.
form = OrderCreateForm(creator_list=dictionary['creator_list'], data=request.POST)
One exception is when form only has one argument - the data. This will help you to avoid messing up with arguments order (which is the reason of your errors here).
How can I clean the data in a form and have the cleaned data redisplayed instead of the submitted data?
There are several fields in my form, and every time the user submits it, it should be redisplayed with the values the user entered. However, some of the fields I would like to clean and update for the user. More specifically, I have a field FriendlyIntegerField(forms.CharField) in which I override to_python to not only call int(str(value)), but also set any negative number to 0 etc. I do not want to redisplay the form with the invalid data and have the user fix it himself (which is how Django wants me to do it).
I don't have a problem cleaning the data and use it for the rest of my view-function, but how can I update the actual form with this data?
By the way, the form does not reflect a structure in my data model, and so inherits from Form, not ModelForm.
Edit:
My Field (in a stripped down version) looks like this:
class FriendlyIntegerField(forms.CharField):
def to_python(self, value):
try:
return str(int(str(value).replace(' ','')))
except:
raise forms.ValidationError('some error msg')
My Form (in a stripped down version) looks like this:
class SearchForm(forms.Form):
price_from = FriendlyIntegerField()
price_to = FriendlyIntegerField()
And my view:
def search(request, key):
if request.method == 'POST':
form = SearchForm(request.REQUEST)
if not form.is_valid():
print "Form not valid"
else:
form = SearchForm()
return render_to_response('path_to_template', {'form' : form}
If, after you've cleaned your form with is_valid(), you render that cleaned form with your view, rather than redirect to a new page, you'll see the cleaned data in your page.
(If you wanted the user to see this cleaned data and then properly submit it, you could use a hidden field to track whether the form data has already been cleaned, but this isn't without complications...)