Django file input from Forms not reaching model - python

I am a noob in django and am not able to figure out the issue in my code. Basically I am trying to upload some images to my server using a form. Whenever I save the form, it is only taking the default image specified in my model, not the images I uploaded in my form.
print(request.Files) before calling form.is_valid() is giving output as:
<MultiValueDict: {'id_image01': [<InMemoryUploadedFile: image01.jpg (image/jpeg)>], 'id_image02': [<InMemoryUploadedFile: image02.jpg (image/jpeg)>], 'id_image03': [<InMemoryUploadedFile: image04.jpg (image/jpeg)>], 'id_image04': [<InMemoryUploadedFile: image07.jpg (image/jpeg)>]}>
which I presume says that the image is correctly loaded in the memory.
My code goes like this:
views.py
def create_puzzle(request):
if request.method == 'POST':
print(request.FILES)
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
puzzleImages= form.save(commit=False)
puzzleImages.username= request.user.username
puzzleImages.save()
return redirect('/puzzle/index.html')
else:
form = DocumentForm()
return render(request, 'puzzle/create_puzzle.html', {
'form': form
})
my Forms.py
class DocumentForm(forms.ModelForm):
class Meta:
model = Document
fields = ('birthday_message', 'video_url', 'image01',
'image02', 'image03','image04',
'image05', 'image06', 'image07',
'image08', 'image09', 'image10')
captcha = ReCaptchaField(
public_key='###########',
private_key='###########',
)
my models.py
class Document(models.Model):
description = models.CharField(max_length=255, blank=True)
image01 = models.ImageField(default='default/image01.png', upload_to=user_directory_path)
image02 = models.ImageField(default='default/image02.png', upload_to=user_directory_path)
image03 = models.ImageField(default='default/image03.png', upload_to=user_directory_path)
image04 = models.ImageField(default='default/image04.png', upload_to=user_directory_path)
image05 = models.ImageField(default='default/image05.png', upload_to=user_directory_path)
image06 = models.ImageField(default='default/image06.png', upload_to=user_directory_path)
image07 = models.ImageField(default='default/image07.png', upload_to=user_directory_path)
image08 = models.ImageField(default='default/image08.png', upload_to=user_directory_path)
image09 = models.ImageField(default='default/image09.png', upload_to=user_directory_path)
image10 = models.ImageField(default='default/image10.png', upload_to=user_directory_path)
username = models.CharField(max_length=255, blank=False)
birthday_message=models.CharField(default='Happy Birthday Special One!!!', max_length=500)
video_url=models.CharField(default='https://www.youtube.com/watch?v=A9jl1yLURp4', max_length=100)
uploaded_at = models.DateTimeField(auto_now_add=True)
def save(self, *args, **kwargs):
#print (self.image01)
#print(self.birthday_message)
if not self.id:
self.image01 = self.compressImage(self.image01, "image01")
self.image02 = self.compressImage(self.image02, "image02")
self.image03 = self.compressImage(self.image03, "image03")
self.image04 = self.compressImage(self.image04, "image04")
self.image05 = self.compressImage(self.image05, "image05")
self.image06 = self.compressImage(self.image06, "image06")
self.image07 = self.compressImage(self.image07, "image07")
self.image08 = self.compressImage(self.image08, "image08")
self.image09 = self.compressImage(self.image09, "image09")
self.image10 = self.compressImage(self.image10, "image10")
super(Document, self).save(*args, **kwargs)
Thank you for your help!

Please add below line of code in your template
<form enctype="multipart/form-data" method="post">
-----
----
----
</form>

I found out the cause of the issue. I wanted to render form fields manually and was using to get the files. It would pass the text inputs normally but when it came to files, it would not validate them. So, instead of having a tag such as:
<input name="id_image03" type="file" class="form-control" id="id_image03">
I did:
{{ form.image03 }}
That was it!

Related

Django AssertionError when testing Create View

I'm running some tests for my app 'ads', but when I try to test the CreateView it fails with the following message:
AssertionError: 'just a test' != 'New title'
Here's the test:
class AdTests(TestCase):
def setUp(self):
self.user = get_user_model().objects.create_user(
username='test_user',
email='test#email.com',
password='secret'
)
self.ad = Ad.objects.create(
title='just a test',
text='Ehy',
owner=self.user
)
def test_ad_create_view(self):
response = self.client.post(reverse('ads:ad_create'), {
'title': 'New title',
'text': 'New text',
'owner': self.user.id,
})
self.assertEqual(response.status_code, 302)
self.assertEqual(Ad.objects.last().title, 'New title')
self.assertEqual(Ad.objects.last().text, 'New text')
So it could be that the test fails in creating a new ad, and then it compares the fields with the first ad in the setUp method.
I upload the rest of the code if it can help:
urls.py
from django.urls import path, reverse_lazy
from . import views
app_name='ads'
urlpatterns = [
path('', views.AdListView.as_view(), name='all'),
path('ad/<int:pk>', views.AdDetailView.as_view(), name='ad_detail'),
path('ad/create',
views.AdCreateView.as_view(success_url=reverse_lazy('ads:all')), name='ad_create'),
...
]
models.py
class Ad(models.Model) :
title = models.CharField(
max_length=200,
validators=[MinLengthValidator(2, "Title must be greater than 2 characters")]
)
price = models.DecimalField(max_digits=7, decimal_places=2, null=True)
text = models.TextField()
"""We use AUTH_USER_MODEL (which has a default value if it is not specified in settings.py) to create a Foreign Key relationship between the Ad model
and a django built-in User model"""
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
comments = models.ManyToManyField(settings.AUTH_USER_MODEL, through='Comment', related_name='comments_owned')
picture = models.BinaryField(null=True, editable=True)
tags = TaggableManager(blank=True)
content_type = models.CharField(max_length=256, null=True, help_text='The MIMEType of the file')
favorites = models.ManyToManyField(settings.AUTH_USER_MODEL, through='Fav', related_name='favorite_ads')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
views.py
class AdCreateView(LoginRequiredMixin, View):
template_name = 'ads/ad_form.html'
success_url = reverse_lazy('ads:all')
def get(self, request, pk=None):
form = CreateForm()
ctx = {'form': form}
return render(request, self.template_name, ctx)
# Pull data
def post(self, request, pk=None):
form = CreateForm(request.POST, request.FILES or None)
if not form.is_valid():
ctx = {'form': form}
return render(request, self.template_name, ctx)
pic = form.save(commit=False)
pic.owner = self.request.user
pic.save()
form.save_m2m()
return redirect(self.success_url)
forms.py (the view uses it especially to check and save the image)
class CreateForm(forms.ModelForm):
max_upload_limit = 2 * 1024 * 1024
max_upload_limit_text = naturalsize(max_upload_limit)
# Call this 'picture' so it gets copied from the form to the in-memory model
# It will not be the "bytes", it will be the "InMemoryUploadedFile"
# because we need to pull out things like content_type
picture = forms.FileField(required=False, label='File to Upload <= ' + max_upload_limit_text)
upload_field_name = 'picture'
class Meta:
model = Ad
fields = ['title', 'text', 'price', 'picture', 'tags']
# Check if the size of the picture is less than the one specified (see above).
def clean(self):
cleaned_data = super().clean()
pic = cleaned_data.get('picture')
if pic is None:
return
if len(pic) > self.max_upload_limit:
self.add_error('picture', "File must be < " + self.max_upload_limit_text + " bytes")
# Convert uploaded File object to a picture
def save(self, commit=True):
instance = super(CreateForm, self).save(commit=False)
# We only need to adjust picture if it is a freshly uploaded file
f = instance.picture # Make a copy
if isinstance(f, InMemoryUploadedFile): # Extract data from the form to the model
bytearr = f.read()
instance.content_type = f.content_type
instance.picture = bytearr # Overwrite with the actual image data
if commit:
instance.save()
self.save_m2m()
return instance
I hope it is useful, thanks in advance!
According to Django Doc
Create View:
A view that displays a form for creating an object, redisplaying the form with validation errors (if there are any) and saving the object.
This is not a Valid way to do create View based on my experience. Check the doc Doc here.
if i understand what you are talking about You want to submit Ad Model using Create View, if you want to submit it in form You can something like this:
from django.views.generic import CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy,
class PostCreativeView(LoginRequiredMixin, CreateView):
model = #Your Model
fields = [#Fields of the model You want to submit]
template_name = #html template you want to submit the form
success_url = reverse_lazy(#url for redirected user when the form is submitted)
def form_valid(self, form):
form.instance.user = self.request.user
return super (PostCreativeView, self).form_valid(form)
in the form template you can add:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Save">
</form>
for styling you can follow this: Answer

How do I upload an image with Django with an ImageField?

guys.
I have a problem where I want to change the thumbnail on my website of a Model (talking about 3D printing here but also Django ones) but Django does not change it.
models.py
class Model3D(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=300)
description = models.TextField(max_length=800, blank=True)
thumbnail = models.ImageField(upload_to='models/thumbnails', null=True)
I tried two different forms.py
Version 1
class ImageUploadForm(forms.Form):
"""Image upload form."""
image = forms.ImageField()
Version 2 (ignore the following name, it was just a fast test)
class ModelCreateForm(forms.ModelForm):
class Meta:
model = Model3D
fields = ['name', 'thumbnail']
help_texts = {
'thumbnail': ("Gebe hier den Namen des Modelles ein.")
}
views.py
def save_settings(request, pk):
model = get_object_or_404(Model3D, pk=pk)
if request.method == 'POST':
# print("request.method == 'POST'" + str(dir(saveModel)))
model.name = request.POST.get('name', model.name)
model.description = request.POST.get('description', model.description)
form = ImageUploadForm(request.POST, request.FILES)
if form.is_valid():
model.thumbnail = form.cleaned_data['image']
model.save()
return HttpResponse('image upload success')
model.save()
return redirect('settings_model', pk=model.pk)
return redirect('settings_model', pk=model.pk)
I do have enctype="multipart/form-data in my template. Please help!

How to save a string variable from views in Django

I'm trying to use pytesseract for my Django application. In views.py I called pytesseract and stored all the text it found in the 'text_content' variable. I want to save this variable as the 'text' parameter for my model, but I'm not sure how to go about this.
I tried to use .save(), but got this error:
'str' object has no attribute 'save'
Here is views.py
def image_view(request):
if request.method == 'POST':
form = partForm(request.POST, request.FILES)
if form.is_valid():
form.save()
data = request.POST.copy()
image_file = request.FILES.get('image')
text_content = pytesseract.image_to_string(Image.open(image_file))
text_content.save()
return redirect('success')
else:
form = partForm()
return render(request, 'add_image.html', {'form' : form})
Here is models.py
class Component(models.Model):
snum = models.CharField(max_length=20, default = '')
image = models.ImageField(blank=True)
text = models.TextField(default = 'no text found')
Here is forms.py
class partForm(forms.ModelForm):
snum = forms.CharField(max_length=128, help_text="please enter the
number.")
class Meta:
model = Component
fields = ['snum', 'image']
The error is occuring because of this line. text_content.save()
You're trying to call save function on a string object.
Now to solve your problem, there are two ways to do it. One is to manipulate the request data and send it to the form, other is to do in the save method of your model.
Way 1: in your current view
if form.is_valid():
data = request.POST.copy()
image_file = request.FILES.get('image')
text_content = pytesseract.image_to_string(Image.open(image_file))
data['relevant_field_name'] = text_content
new_form = partForm(data)
if new_form.is_valid():
new_form.save()
return redirect('success')
Way 2: in models.py, add this to Component
def save(self, *args, **kwargs):
if getattr(self, 'image'):
image_file = self.image
self.relevant_field_name = pytesseract.image_to_string(Image.open(image_file))
super(Component, self).save(*args, **kwargs)

ModelForm right approach for editing database record?

Can someone help me with fixing Django ModelForm?
This particular code can add new item to database as expected, but when I'm trying to edit db record - It just add new record, instead of updating old. I'm quite new in Django framework.
views.py:
def manage(request, item_id = None):
t = get_object_or_404(Hardware, id=item_id) if item_id else None
form = Manage(request.POST or None, instance=t)
if t:
if form.is_valid():
#form.save()
hostname = form.cleaned_data['hostname']
cpu = form.cleaned_data['cpu']
os = form.cleaned_data['os']
ram = form.cleaned_data['ram_total']
storage = form.cleaned_data['storage']
hostdata = Hardware(
hostname=hostname,
cpu=cpu,
ram_total=ram,
os=os,
storage=storage,
lock_state=t.lock_state, # because in edit operation we shouldn't change it.
lock_date=t.lock_date, # because in edit operation we shouldn't change it.
locked_by=t.locked_by) # because in edit operation we shouldn't change it.
hostdata.save()
return HttpResponseRedirect(reverse('main:index'))
elif not t:
if form.is_valid():
hostname = form.cleaned_data['hostname']
cpu = form.cleaned_data['cpu']
os = form.cleaned_data['os']
ram = form.cleaned_data['ram_total']
storage = form.cleaned_data['storage']
current_user = request.user
user = User.objects.get(id=current_user.id)
hostdata = Hardware(
hostname=hostname,
cpu=cpu,
ram_total=ram,
os=os,
storage=storage,
lock_state=0,
lock_date=datetime.datetime.now(),
locked_by=user)
hostdata.save()
return HttpResponseRedirect(reverse('main:index'))
return render(request, 'hardware/edit.html', {'form': form})
models.py:
class Hardware(models.Model):
hostname = models.CharField(max_length=255, default=None)
os = models.CharField(max_length=255, default=None)
cpu = models.CharField(max_length=255, default=None)
ram_total = models.CharField(max_length=255, default=None)
storage = models.CharField(max_length=255, default=None)
lock_state = models.BooleanField(default=0)
locked_by = models.ForeignKey(User)
lock_date = models.DateTimeField(default=None)
alive = models.BooleanField(default=0)
class Meta:
db_table = "hardware"
def __str__(self):
return self.hostname
forms.py:
class Manage(forms.ModelForm):
class Meta:
model = Hardware
fields = ['hostname', 'os', 'cpu', 'ram_total', 'storage']
urls.py:
url(r'^manage/new/$', views.manage, name='add'),
url(r'^manage/edit/(?P<item_id>[0-9]+)/$', views.manage, name='edit')
template:
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Save!" />
</form>
You already retrieved the instance t in the first line of your view. The code below will always create a new instance (unless you specify the pk parameter):
hostdata = Hardware(...)
hostdata.save()
Simply do this instead:
if t:
if form.is_valid():
t.hostname = form.cleaned_data['hostname']
t.cpu = form.cleaned_data['cpu']
....
t.save()
However, you really should rely on the save method provided by the ModelForm as the other answers suggested. Here's an example:
def manage(request, item_id=None):
t = get_object_or_404(Hardware, id=item_id) if item_id else None
# if t is None, a new object will be created in form.save()
# if t is an instance of Hardware, t will be updated in form.save()
form = Manage(request.POST, instance=t)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('main:index')
return render(request, 'hardware/edit.html', {'form': form})
You also specified fields in your form:
fields = ['hostname', 'os', 'cpu', 'ram_total', 'storage']
These are the fields which will be set or updated when you call form.save().
I think something like this - using update_fields - should work:
def manage(request, item_id = None):
t = get_object_or_404(Hardware, id=item_id)
form = Manage(request.POST or None, instance=t)
if t:
if form.is_valid():
#form.save()
t.hostname = form.cleaned_data['hostname']
t.cpu = form.cleaned_data['cpu']
t.os = form.cleaned_data['os']
t.ram = form.cleaned_data['ram_total']
t.storage = form.cleaned_data['storage']
t.save(update_fields=['hostname', 'cpu', 'os','ram','storage'])
return HttpResponseRedirect(reverse('main:index'))
........
Try Class Based View, which in it's simplest looks like:
from django.views import generic
class HardwareEditView(generic.UpdateView):
template_name = "hardware.html"
form_class = Manage
You will have to add get_absolute_url to the model.
Generic class based views are exactly for this standard create/update/view common tasks.

Passing parameters to Django view through url

I am trying to create a formset that captures a parameter from the url to prefill one of the fields. The form is displaying correctly and at the correct address, but after clicking "submit" the page redirects to "/correction/" instead of the intended /correction/A07686+dwdID19, and the form does not save. What might be the issue?
In models.py:
class correction(models.Model):
corrected_word = models.ForeignKey(item)
correction_author = models.ForeignKey(User)
correction_made = models.IntegerField(u'correction_made', choices=CORRECTION_CHOICES)
correction_word = models.CharField(u'correction_word', max_length=200, blank=True, null=True)
time = models.DateTimeField(auto_now_add=True)
approved = models.IntegerField(u'approved', choices=APPROVAL_CHOICES, blank=True, null=True)
def __unicode__(self):
return str(self.time)
In views.py:
def submit_corr(request, bdword):
if hasattr(request, 'user') and request.user.is_authenticated():
word = item.objects.filter(file_position=bdword)[0]
CorrFormSet = inlineformset_factory(item, correction, fields=['correction_made', 'correction_word','correction_author'], can_delete=False, extra=1)
form = CorrFormSet(request.POST, request.FILES, instance=word, initial=[{'correction_author': request.user,}])
if request.method == 'POST':
if form.is_valid():
for entry in form:
entry.save()
else:
form = CorrFormSet(instance=word, initial=[{'correction_author': request.user,}])
return render(request, "correctionform.html", {"form": form,"word": word})
In urls:
url(r'^correction/(?P<bdword>.*)$', 'english.views.submit_corr'),
In the template:
Submit correction</th>
Thanks in advance!
Submit correction doesn't submit a form because it's a link and when you press a link, this sends a request with a GET method to the server so never enter to if request.method == 'POST': .
Please try something like
<form action="/correction/{{word.file_position}}" method="POST">
{# your inputs fields#}
<button type="submit">Submit correction</button>
</form>
I hope to be useful. Regards

Categories