I have view to document edit:
#login_required
def document_edit(request, doc_id):
try:
doc = Document.objects.get(id=doc_id)
except Document.DoesNotExist:
raise Http404
form = DocumentForm(instance=doc)
if request.method == "POST":
form = DocumentForm(request.POST, request.FILES, instance=doc)
if form.is_valid():
if request.POST.get('cancel'):
return HttpResponseRedirect('/')
if request.POST.get('delete'):
document = Document.objects.get(id=doc_id)
document.file.delete()
document.delete()
return HttpResponseRedirect('/')
else:
form.save(author=request.user)
text = "Document edited!"
return render_to_response('message.html', {'text' : text}, context_instance = RequestContext(request))
else:
return render_to_response('document_edit.html', {'form':form,}, context_instance = RequestContext(request))
else:
form = DocumentForm(instance=Document.objects.get(id=doc_id))
return render_to_response('document_edit.html', {'form':form, 'doc':doc,}, context_instance = RequestContext(request))
and this form in forms.py:
class DocumentForm(ModelForm):
file = forms.FileField(label = 'file', required=True, error_messages={'required' : 'required!','empty': "this file is empty!"})
title = forms.CharField(label = 'title', widget = forms.TextInput(attrs={'size': 93,}), error_messages={'required': 'required!'})
description = forms.CharField(label = 'description', widget = forms.Textarea(attrs={'rows': 10, 'cols': 120,}), error_messages={'required': 'required!'})
editable = forms.BooleanField(label='Document editable?', widget=forms.RadioSelect(choices=YES_OR_NO), required=False, initial=True)
class Meta:
model = Document
exclude = ('author',)
def save(self, author, commit=True):
document=ModelForm.save(self,commit=False)
document.author = author
if commit:
document.save()
return document
Now i want possibility to override existed file (and automatically deleting previous file). How can i do that?
If I understand correctly, you are looking to replace any previous file in the 'file' field (a FileField datatype) of a Document Model. That will happen automatically if you upload a new file using the form. Are you seeing different behavior?
Automatically deleting the previous files depends on the Django version you are using. As of 1.3 (currently the most recent) FileField will not delete any files (changelog). In 1.2 and prior, the FileField would remove the file from the filesystem on change and on delete. So if you change or delete the Model instance in 1.2, the file goes away. Under 1.3, the file stays.
You are doing the right thing by calling doc.file.delete(). However, if you do any sort of bulk operation on model instances, this won't be enough. To test in 1.3, use the AdminSite to select multiple records and select the delete dropdown. While the records will be gone, the files will still exist. These bulk QuerySet operations and any delete operation that skips your custom document_edit() function will leave behind the files. Your best bet will be to write a removal function for all FileField datatypes and attach it with a pre/post_delete signal. If you need help with this, I suggest a new SO Question.
Other things: You can tidy your code with this shortcut function
from django.shortcuts import get_object_or_404
doc = get_object_or_404(Document, id=doc_id)
You use Document.objects.get() three times in your posted code. Just use the above shortcut once and then use the 'doc' variable in all other locations
Related
I have a Form (Formset) for users to update their profiles. This is a standard User model form, and custom Participants model form. Now, in cases when a participant provide his phone number, I need to refresh the whole Form with a new 'Code' filed dynamically. And the participant will type the code he received my SMS.
Here is how I am trying to do it:
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
form.save()
seller_form = SellerForm(self.request.POST, instance=self.object.seller)
if seller_form.is_valid():
seller = self.request.user.seller
seller.inn = seller_form.cleaned_data.get('inn')
if seller_form.cleaned_data.get('phone_number'):
seller_form.fields['code'] = models.CharField(max_length=4)
return render(request, self.template_name, {'form': form, 'seller_form': seller_form})
seller.save()
return HttpResponse('Seller updated')
return render(request, self.template_name, {'form': form, 'seller_form': seller_form})
Well I am not sure if this is the way I can add additional field. What would you suggest to handle this situation?
A technique I have used is to have an initially hidden field on the form. When the form otherwise becomes valid, I cause it to become visible, and then send the form around again. In class-based views and outline:
class SomeThingForm( forms.Modelform):
class Meta:
model=Thing
fields = [ ...
confirm = forms.BooleanField(
initial=False, required=False, widget=forms.widgets.HiddenInput,
label='Confirm your inputs?' )
class SomeView( CreateView): # or UpdateView
form_class = SomeThingForm
template_name = 'whatever'
def form_valid( self, form):
_warnings = []
# have a look at cleaned_data
# generate _warnings (a list of 2-tuples) about things which
# aren't automatically bad data but merit another look
if not form.cleaned_data['confirm'] and _warnings:
form.add_error('confirm', 'Correct possible typos and override if sure')
for field,error_text in _warnings:
form.add_error( field, error_text) # 'foo', 'Check me'
# make the confirm field visible
form.fields['confirm'].widget = forms.widgets.Select(
choices=((0, 'No'), (1, 'Yes')) )
# treat the form as invalid (which it now is!)
return self.form_invalid( form)
# OK it's come back with confirm=True
form.save() # save the model
return redirect( ...)
For this question, I think you would replace confirm with sms_challenge, a Charfield or IntegerField, initially hidden, with a default value that will never be a correct answer. When the rest of the form validates, form_valid() gets invoked, and then the same program flow, except you also emit the SMS to the phone number in cleaned_data.
_warnings = []
# retrieve sms_challenge that was sent
if form.cleaned_data['sms_challenge'] != sms_challenge:
_warnings.append( ['sms_challenge', 'Sorry, that's not right'] )
if _warnings:
...
form.fields['sms_challenge'].widget = forms.widgets.TextInput
return self.form_invalid( form)
I think that ought to work.
I have started learning Django recently using a Udemy course. While going through the course instructor asked to save values from a Form to database.
After searching on the internet I figured out how to put form values into database and everything is working fine. Below is my views.py and forms.py files.
forms.py
class FormName(forms.Form):
fname = forms.CharField( label="First Name")
lname = forms.CharField(label="Last name:")
email = forms.EmailField()
verify_email = forms.EmailField(label='Verify Email:')
def clean(self):
all_clean_data = super().clean()
email = all_clean_data['email']
vmail = all_clean_data['verify_email']
if email != vmail:
raise forms.ValidationError("Check the emails")
views.py
def signup(request):
form = forms.FormName()
if request.method == 'POST':
form = forms.FormName(request.POST)
if form.is_valid():
post = User()
post.fname=request.POST.get('fname')
post.lname=request.POST.get('lname')
post.email=request.POST.get('email')
post.save()
return render(request,'third_app/greet.html')
else:
return render(request,'third_app/oops.html',{'form':form})
return render(request, 'third_app/signup.html',{'form':form})
Now coming to question, the instructor is using Meta class to store the form values to the database. Below are his forms.py and views.py files. I am curious about what the difference is between my method and the instructor's.
forms.py
class FormName(forms.ModelForm):
class Meta():
model = User
fields = 'all'
views.py
def signup(request):
form = forms.FormName()
if request.method == 'POST':
form = forms.FormName(request.POST)
if form.is_valid():
form.save(commit=True)
return render(request,'third_app/greet.html')
else:
return render(request,'third_app/oops.html',{'form':form})
return render(request, 'third_app/signup.html',{'form':form})
Thanks.
The Django docs explain this very well. It's what is known as a ModelForm:
If you’re building a database-driven app, chances are you’ll have forms that map closely to Django models. For instance, you might have a BlogComment model, and you want to create a form that lets people submit comments. In this case, it would be redundant to define the field types in your form, because you’ve already defined the fields in your model.
For this reason, Django provides a helper class that lets you create a Form class from a Django model.
So, to answer your question, your method uses a regular form (forms.Form) where you define the form fields, perform validation and then save each field individually in your view. When using form.ModelForm, field validation and saving is taken care of for you. Seeing as you have already defined what your fields are, the ModelForm uses this to perform the validation. The save() method conveniently saves each field to the database.
I run into this pattern all the time and I'm wondering if there's a better way to handle it. Lots of my models contemplate the idea of 'creator' - in other words, I want to make sure the user who created the object is saved as the creator. As such, my models almost always include
creator = models.ForeignKey(User)
There doesn't seem to be a way of defaulting this user to the user who created it (request.user). As such I find myself building model forms and adding creator as a HiddenInput()
class MyModelForm(ModelForm):
class Meta:
model = MyModelForm
fields = ['name', 'creator']
widgets = {
'creator': HiddenInput()
}
and then binding the form in the view with initial
form = MyModelForm(initial={'creator': request.user})
and checking on POST to make sure no one dickered with the form (full view):
def create(request):
form = MyModelForm(initial={'creator': request.user})
if request.method == 'POST':
if int(request.POST['creator']) != int(request.user.id):
return render(request,'500.html', {'message':'form tampering detected'})
form = FeedGroupForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('index'))
return render(request, 'mymodelformpage.html',{'form':form})
I'm fine with this but it seems like an anti-pattern. It strikes me that there ought to be a way to default the creator in the model.
creator = models.ForeignKey(User, default=request.user)
Or do it in a post_save perhaps?
No, this is not the right way. The correct way is to exclude the creator field from the form, then set it on save:
if form.is_valid:
obj = form.save(commit=False)
obj.creator = request.user
obj.save()
... redirect ...
I am unable to upload the file. I am getting
Type error builtin_function_or_method' object is not iterable
models.py
class seeker(models.Model):
user = models.OneToOneField(User)
birthday = models.DateField()
class Upload(models.Model):
user = models.ForeignKey(Seekers)
resume = models.FileField(upload_to ='resume', blank = True, null = True)
forms.py
class SeekersForm(forms.Form):
resume = forms.FileField(label = 'Select a file',help_text = 'max.3 MB')
views.py
def List(request):
# Handle file upload
if request.method == 'POST':
form = SeekersForm(request.POST, request.FILES)
if form.is_valid():
#id = User.object.get(id)
newdoc = Seekers.objects.get(user_id)
newdoc.resume =Upload(resume = request.FILES['resume'])
newdoc.save()
#seekers_edit = Seekers.objects.get(id)
#seekers_edit.resume = Seekers(resume = request.FILES['resume'])
#seekers_edit.save()
#Redirect to the document list after POST
return HttpResponseRedirect('/profile/')
else:
form = SeekersForm() # A empty, unbound form
#Load documents for the list page
seekers = Seekers.objects.all()
#Render list page with the documents and the form
return render_to_response('list.html',{'seekers':seekers,'form':form},context_instance=RequestContext(request))
It's hard to say where your problem is, but I think the following line of code is the main problem:
newdoc.resume =Upload(resume = request.FILES['resume'])
You have to save a file in a FileField explicitly before you save the entire model instance. Also, if you have a ForeignKey field in one of your models and you want to assign it an instance of another model, please save that instance first before you do the assignment. Without knowing your Seekers model, all I can do is guessing what might help you. Something like the following might get you started:
your_file = request.FILES['resume']
upload_instance = Upload()
upload_instance.resume.save(name=your_file.name, content=your_file, save=False)
upload_instance.user = ... # Here goes an instance of your Seekers model
upload_instance.save() # Here you save the whole instance of your Upload model
Also, please note the following:
Your model Seekers should rather be named Seeker using the singular, not the plural. This should generally be like that with all your models.
Python functions should always start with a lowercase letter, i.e. list instead of List. However, this name is a bad choice here anyway, because a function called list is already present in Python's standard library.
Please take a closer look at Django's documentation. It's all in there what you need to know. I recommend you to read especially these sections:
https://docs.djangoproject.com/en/1.4/ref/models/fields/#filefield
https://docs.djangoproject.com/en/1.4/ref/files/file/
Problems in your code:
Your form definition duplicates information from your model — just use forms.ModelForm (with exclude so as not to display the user field)
As currently pasted, newdoc = Seekers.objects.get(user_id) will raise a TypeError ('foo' object is not iterable); .get() accepts keyword parameter filters, not anything else.
Accessing request.FILES['resume'] manually isn't necessary or recommended
So, in short, you're almost there; just let Django forms do more of the work for you:
# forms.py
class SeekerForm(forms.ModelForm)
class Meta:
model = Seeker
# views.py
def seeker_list(request):
# Opinions are divided as to whether it's ever appropriate to
# modify the database like this on a GET request, but it seems
# to make sense here
seeker = Seekers.objects.get_or_create(user=request.user)
if request.method == 'POST':
form = SeekerForm(request.POST, request.FILES, instance=seeker)
if form.is_valid():
form.save()
return HttpResponseRedirect('/profile/')
else:
form = SeekerForm(instance=seeker)
seekers = Seekers.objects.all()
#Render list page with the documents and the form
return render_to_response('list.html', {
'seekers':seekers,
'form':form
}, context_instance=RequestContext(request))
It's not clear what the significance (if any) of the commented-out sections of your code is — I've assumed you always want to modify the current user's Seeker, but if not then adapt as appropriate.
I have a modelform that my views generate an HTML form for editing content. Currently, it's able to pull in the current stored text content, like this:
#login_required
def edit_person(request, s_id, p_id):
p = get_object_or_404(Person, id=p_id)
if request.method == 'POST':
form = PersonForm(request.POST, request.FILES)
if form.is_valid():
p.name = request.POST['name']
p.title = request.POST['title']
handle_uploaded_file(request.FILES['photo'], request.FILES['photo'].name, 'media/images/people/')
p.photo = request.FILES['photo']
p.order = request.POST['order']
p.save()
return HttpResponseRedirect('/section/'+s_id+'/')
else:
return HttpResponse("error")
else:
form = PersonForm({ 'name': p.name, 'title': p.title, 'photo': p.photo, 'order': p.order })
return render_to_response('auth/edit-form.html', { 'form': form }, context_instance=RequestContext(request))
return HttpResponseRedirect('/section/'+s_id+'/')
However, the photo file path is blank. I don't want the user to have to upload a new file every time they edit something if they don't want to change the image. How do I get the file upload field to appear pre-populated and not overwrite itself if they don't change it? Thanks.
Believe it or not, it can be done! However, it requires the use of a custom django app called django-file-resubmit
Note that app as given only works for the widgets in admin and requires sorl-thumbnail.
You may prefer to use my fork:
https://github.com/JordanReiter/django-file-resubmit
It's a general-purpose version for use everywhere a ModelForm is used that doesn't have any other prerequisites.
It's pretty cool in that it automagically stores the file on submission (even if there is a validation error) and retrieves it from the cache when the widget is rendered in the form.
This is literally all you have to do to implement it:
import file_resubmit.widgets
class PersonForm:
""" existing code here """
photo = forms.ImageField(required=False, widget=file_resubmit.widgets.ResubmitImageWidget())