Django file upload: filename not sticking - python

I'm uploading files and storing metadata in a db. Part of the metadata is the file name itself. However, somewhere down the line, the filename seems to not be getting saved! I will paste only what I think are relevant parts of the code to keep this short.
class UploadFile(models.Model):
...
theFile = models.FileField(upload_to = "Genius/Uploads/", null = True)
filename = models.CharField(max_length = 50, blank = True, null = False)
class UploadFileForm(ModelForm):
class Meta:
model = UploadFile
fields = ('title', 'theFile', 'date_uploaded',) # Don't prompt for filename
def files_upload(request):
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
form.filename = request.FILES['theFile'].name # TODO: sanitize!
# form.filename = 'foo'
form.save()
return HttpResponseRedirect('/files/upload/successful/')
else:
form = UploadFileForm()
return render_to_response('files/upload_file.html', { 'form': form })
I have checked the value of request.FILES['theFile'].name before & after saving the form. For whatever reason it is intact but never seems to make it into the DB.

That's because form.filename is the form field, not the value it will be saving.
You are looking for something like this:
class UploadFileForm(ModelForm):
def save(self, commit=True):
instance = ModelForm.save(self, commit=False)
instance.filename = self.files['theFile'].name
if commit:
instance.save()
return instance
class Meta:
model = UploadFile
fields = ('title', 'theFile', 'date_uploaded',) # Don't prompt for filename
Alternative solution:
upload_file = form.save(commit=False)
upload_file.filename = request.FILES['theFile'].name
upload_file.save()

Form field values aren't accessed via attributes on the form. So setting 'form.filename' doesn't set the value to be saved in the filename field. Instead, set the value on the instance returned by form.save().
upload_file = form.save(commit=False)
upload_file.filename = filename
upload_file.save()

I just wanted to add that, in the future, you might try to avoid putting such business logic on the model form. While WoLpH's answer is correct and a great example of how to handle additional model instance processing through ModelForm, particular cases of having fields dependent on other fields data is handled in the Model, Form and ModelForm API through their respected clean() methods and is mentioned in several places in the official reference docs (here's one on forms, though the same holds true for the Model and ModelForm APIs).
In your case this would mean:
import os
class UploadFile(models.Model):
# ...
def clean(self):
# Field data has already been populated by this point.
# Note that `FieldFile` inherits from `File` and that
# `File.name` is actually the full path to the file
# so we need to get the base path component sans the extension
path, extension = os.path.splitext(self.thefile.file.name)
self.filename = os.path.basename(path)
And that's about it! If you properly set the editable attribute on your model fields, you'll find that you can rely on Django to automatically generate the ModelForm for the UploadFile model. That means you don't have to define a ModelForm for the generic create, update views or your model's ModelAdmin and that's less lines of code to manage!
The general rule of thumb is that you think twice about whether overriding default behavior is ever justified or self-contained, especially when your working at the far end of the business logic chain, otherwise you may feel the wrath of unexpected flops.

Related

Ignore Django Model fields in migrations

I want to create a Django model that contains a FileField to store image and video files, but I want to validate the files before saving the instance. I've thought about adding three fields:
file: A FileField field. This will only be used to have a file column in the database, but serializers won't use it (instead they will use the two next fields).
file_image: An ImageField to perform image file validation. Before the model instance is saved, the file will be assigned to the file field. I don't want this field to have a dabatase representation.
file_video: A VideoField (custom field) to perform video file validation. Before the model instance is saved, the file will be assigned to the file field. I don't want this field to have a dabatase representation.
Of course, file_image and file_video won't be set at the same time.
The problem is preventing makemigrations from including file_image and file_video in the migrations. I could edit the migration file by hand, but I wonder if there is any way to automatically ignore these fields.
class MyModel(models.Model):
file = models.ImageField()
file_image = models.ImageField() # Not an actual column
file_video = models.VideoField() # Not an actual column
def save(self, *args, **kwargs):
if self.file_image.file is not None:
self.file.file = self.file_image.file
elif self.file_video.file is not None:
self.file.file = self.file_video.file
else:
raise ValidationError()
super().save(*args, **kwargs)
Your model is a representation of what's in the database. I would advise you not to fight against the ORM in this manner. Instead, I would perform the validation within the form class that's used when creating/updating the instance. With the form you can define the fields, file_video and file_image, then whichever is used, use that field to write to file.
Ok, so I abandoned my original idea of adding extra fields to the model and I only left file field, as suggested by #schillingt. Now I'm using a custom validation in the clean() method to validate the file type.
from django.db import models
from django.db.models.fields.files import ImageFieldFile
from django import forms
from custom_fields import forms as custom_forms
from custom_fields import fields as custom_fields
class MyModel(models.Model):
file = models.ImageField()
def clean(self):
# Get the MIME type to have a hint of the file type
import magic
mime_type = magic.from_buffer(self.file.file.read(1024), mime=True)
if mime_type.startswith('image'):
image_field = ImageFieldFile(self, models.ImageField(),
self.file.name)
image_field.file = self.file.file
if image_field.width:
self.width = image_field.width
self.height = image_field.height
self.media_type = mime_type
else:
raise ValidationError({'file': forms.ImageField.
default_error_messages['invalid_image']})
elif mime_type.startswith('video'):
video_field = custom_fields.VideoFieldFile(self,
custom_fields.VideoField(),
self.file.name)
video_field.file = self.file.file
if video_field.width:
self.width = video_field.width
self.height = video_field.height
self.duration = video_field.duration
self.media_type = mime_type
else:
raise ValidationError({'src': custom_forms.VideoField.
default_error_messages['invalid_video']})
else:
raise ValidationError({'src': 'The file is neither an image nor a video.'})
def save(self, *args, **kwargs):
self.full_clean()
super().save(*args, **kwargs)
I know it's an ugly solution, but could this work? I mean, I tested it and it seems to work, but surely there will be a better and more elegant way.

UPDATE: Dynamic MultipleChoiceField changes instances to strings

I have a django form that has a multiple choice field. The field should be dynamic, that is, only the records associated with the user who is currently logged in should be displayed. I've managed to put this together so far;
forms.py
class myForm(forms.ModelForm):
def __init__(self, someUser, *args, **kwargs):
super(myForm, self).__init__(*args, **kwargs)
someRecords = models.SomeModel.objects.filter(someUser = someUser)
#The line above gets records associated with a specific user
displayNames = []
for i in someRecords:
displayNames.append((i.someField, i.someOtherField + ' ' + i.someOtherField2))
#I am basically making a list of tuples, containing a field and a concatnation of two other fields. The latter will be what is displayed in the select box
self.fields['theSelectField'] = forms.ChoiceField(choices = displayNames)
class Meta:
#I defined model, fields and labels here
views.py
def myFormPage(request):
someUser = request.user.someextensionofuser.someUser
form = forms.myForm(someUser)
context = {'form': form}
if request.method == 'POST':
form = forms.myForm(someUser, data = request.POST)
if form.is_valid():
#Do Stuff if form is valid. However,this stuff doesn't get done, the page refreshes instead
So I've managed to make the select options dynamic. However, now I can't submit data.
EDIT: One of the comments helped me solve the previously stated problem. I've updated the views.py code. However, now I'm running into this error;
Cannot assign "'someString'": "someModel.someField" must be a
"someForeignModel" instance
The option values seem to be strings instead of references to objects. How do I solve this?
This limits the possible options of your select field:
self.fields['theSelectField'].queryset = SomeModel.objects.filter(someUser = someUser)
In your views you might want to use a Class Based View, because it handles a lot of stuff automatically and saves you time. Take a look here: https://ccbv.co.uk/
I firgured it out. Since my main problem was with how the options are displayed to a user, I decided to go with changing my str method in models.py to;
class someModel(models.Model):
#my fields
def __str__(self):
return self.someField + ' ' + self.someOtherField
Then in my forms.py, I went with #dmoe's answer;
self.fields['theSelectField'] = forms.ModelChoiceField(queryset = models.SomeModel.objects.filter(someUser = someUser))
So now both problems are solved. My options have custom labels, and I can submit my data without running into valueError.

save() got an unexpected keyword argument 'commit' Django Error

im geting this error "save() got an unexpected keyword argument 'commit'"
what im trying to do is request user when user upload his files.
update i added my model.py and forms.py and also screen shot of error sorry my fisrt time learning python/django.
screen shot
model.py
class Document(models.Model):
fs = FileSystemStorage(location=settings.MEDIA_ROOT)
input_file = models.FileField(max_length=255, upload_to='uploads', storage=fs)
user = models.ForeignKey(User)
def __unicode__(self):
return self.input_file.name
#models.permalink
def get_absolute_url(self):
return ('upload-delete', )
forms.py
class BaseForm(FileFormMixin, django_bootstrap3_form.BootstrapForm):
title = django_bootstrap3_form.CharField()
class MultipleFileExampleForm(BaseForm):
input_file = MultipleUploadedFileField()
def save(self):
for f in self.cleaned_data['input_file']:
Document.objects.create(
input_file=f
)
here is my views.py
#login_required
def list(request):
# Handle file upload
if request.method == 'POST':
form = MultipleFileExampleForm(request.POST, request.FILES)
if form.is_valid():
newdoc = form.save(commit=False)
newdoc.user = request.user
newdoc.save()
# Redirect to the document list after POST
return HttpResponseRedirect(reverse('myfiles.views.list'))
else:
form = MultipleFileExampleForm() # A empty, unbound form
documents = Document.objects.all
return render_to_response(
'example_form.html',
{'documents': documents, 'form': form},
context_instance=RequestContext(request)
)
You are not sub classing django.forms.ModelForm, yet, you are writing your code like you are.
You need to subclass ModelForm (which has the save method with the commit argument).
Calling super will not work either, as the super class has no save method with that argument.
Remove the commit=False it will never work unless you rewrite your code to subclass django.forms.ModelForm
In any case the save method should always return an instance. I suggest you rename your method to save_all_files or something similar. You will not be able to use commit=False to save multiple object in your save method. It is not the intended use.
For further reading, you can read the source to know how the commit=False works in the ModelForm class at the following address :
https://github.com/django/django/blob/master/django/forms/models.py
I believe you are completely overriding the save method, which gets rid of the existing functionality (i.e. the commit arg). Try running a super() at the end so that it has the existing save functionality as well.
def save(self):
for f in self.cleaned_data['input_file']:
Document.objects.create(
input_file=f
)
super(MultipleFileExampleForm, self).save(*args, **kwargs)

Django - Assign default value to field in ModelForm

In my application I have a CreateView that must initialize some fields of the model with a default value, different from the default defined inside the model.
I do not want the user to edit the value, thus I put the field in the exclude list
class AForm(ModelForm):
class Meta:
model = AModel
exclude = ['a_field']
class AView(CreateView):
form_class = AForm
The question is: where do I set the value of a_field?
I tried to define clean methods inside AForm, like thus
class AForm(ModelForm):
[...]
def clean(self):
d = super(AForm, self).clean()
d['a_field'] = 10
return d
however a_field is set to whatever its default value defined in the model is, rather than 10. I also tried defining clean_a_field, but that is simply not executed.
If I remove a_field from the exclude list, then the clean and clean_a_field will work, but the form won't validate unless I render some <input name="a_field"> inside the template, which is not optimal.
I managed to solve the issue in a way that makes me satisfied, although I'm still not 100% happy with the code.
a_field is a required (by the model) field, thus it is necessary to render an <input name="a_field"> inside the template. The trick was to make a_field non-required:
class AForm(ModelForm):
a_field = Field(required=False,
widget=forms.HiddenInput)
class Meta:
model = AModel
def clean_a_field(self):
return 10
This way I can avoid rendering the field in my template, and the form will still validate. And even if the form is rendered with {{ form.as_p }}, widget=forms.HiddenInput saves my day.
Exclude the field from the form, then in the view you can set the value before you save the form:
form = AForm(request.POST)
if form.is_valid():
new_record = form.save(commit=False)
new_record.a_field = 10
new_record.save()
You also might want to avoid the exclude list and specify which fields you'd like to include with the fields attr of the form definition. See the first paragraph here in the docs.
You set a default value in the model. From the official document,
a_field = models.CharField(max_length=7, default=''), for example
I have a way to Face this situation. Follow the following process:
Remove 'a_field' from the excludes in AForm.
Do not expose 'a_field' in HTML template. i.e. Don't give the user option to change the value via Form in Template. This would ensure that normal user's wont modify the value.
To prevent completely, over-ride get_form_kwargs in the View.
This would provide or over-ride your desired value to 'a_field' and save that
e.g.
class AView(CreateView):
form_class = AForm
def get_form_kwargs(self):
kwargs = super(AView, self).get_form_kwargs()
if self.request.method in {'POST', 'PUT'}:
# Change post data to over-ride or provide value of 'a_field'
data = self.request.POST.copy()
data.update({
'a_field': 'value'
})
kwargs['data'] = data
return kwargs

Django admin error in many-to-many relationship

For example.
class One(models.Model):
text=models.CharField(max_length=100)
class Two(models.Model):
test = models.Integer()
many = models.ManyToManyField(One, blank=True)
When I try save my object in admin panel, I take error such as:
"'Two' instance needs to have a primary key value before a many-to-many relationship can be used."
I use django 1.3. I tried add AutoField to Two class, but it's not work too.
This is my code.
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response, redirect
from django.template import RequestContext
from django.core.urlresolvers import reverse
from project.foo.forms import FooForm
from project.foo.models import Foo
from project.fooTwo.views import fooTwoView
def foo(request, template_name="foo_form.html"):
if request.method == 'POST':
form = FooForm(data=request.POST)
if form.is_valid():
foo = Foo()
foo.name = request.POST.get("name")
foo.count_people = request.POST.get("count_people")
foo.date_time = request.POST.get("date_time")
foo.save()
return fooTwoView(request)
else:
form = FooForm()
return render_to_response(template_name, RequestContext(request, {
"form": form,
}))
P.S. I find my fail. It is in model. I used many-to-many in save method. I add checking before using, but it's not help.
class Foo(models.Model):
name = models.CharField(max_length=100, null=False, blank=False)
count_people = models.PositiveSmallIntegerField()
menu = models.ManyToManyField(Product, blank=True, null=True)
count_people = models.Integer()
full_cost = models.IntegerField(blank=True)
def save(self, *args, **kwargs):
if(hasattr(self,'menu')):
self.full_cost = self.calculate_full_cost()
super(Foo, self).save(*args, **kwargs)
def calculate_full_cost(self):
cost_from_products = sum([product.price for product in self.menu.all()])
percent = cost_from_products * 0.1
return cost_from_products + percent
I try hack in save method such as
if(hasattr(self,Two)):
self.full_cost = self.calculate_full_cost()
This is help me, but i dont think that is the django way. What is interesting, that is without this checking admin panel show error, but create object. Now, if i select item from Two and save, my object does not have full_cost, but when i view my object, admin panel remember my choice and show me my Two item, what i select... I dont know why.
How do i save this?
There are quite a few problems with your code. The most obvious one are
1/ in your view, using a form for user inputs validation/sanitization/conversion then ignoring the santized/converted data and getting unsanitized inputs directly from the request. Use form.cleaned_data instead of request.POST to get your data, or even better use a ModelForm which will take care of creating a fully populated Foo instance for you.
2/ there's NO implicit "this" (or "self" or whatever) pointer in Python methods, you have to explicitely use "self" to get at the instance attributes. Here's what your model's "save" method really do:
def save(self, *args, **kwargs):
# test the truth value of the builtin "id" function
if(id):
# create a local variable "full_cost"
full_cost = self.calculate_full_cost()
# call on super with a wrong base class
super(Banquet, self).save(*args, **kwargs)
# and exit, discarding the value of "full_cost"
Now with regard to your question: Foo.save is obviously not the right place to compute someting based on m2m related objects. Either write a distinct method that run the computation AND update Foo AND save it and call it after the m2m are saved (hint : a ModelForm will take care of saveing the m2m related objects for you), or just use the m2m_changed signal.
This being said, I strongly suggest you spend a few hours learning Python and Django - it will save you a lot of time.
Why not use "OneToOneField" instead of Many-to-Many

Categories

Resources