I am new to Django and would like to know what is the Django-way to add elements in a database not by entering each field from an html form (like it is done by default) but uploading a single file (for example a json file) that will be used to populate the database?
So let say the model has only three fields: title,description,quantity.
And I have a text file (myFile.txt) with "myTitle:myDesc" written in it.
What I want is just a FileField that will accept a text file so I can upload myFile.txt and the title and description will be read from this file.
And at the same time the quantity will be asked "normally" in a text input as it would be by default (only title and description are read from the file).
Of course, validation on the file will be done to accept/deny the uploaded file.
The problem I am facing is that if I add a FileField to the model, the file will be stored in the local storage.
I want the content of the uploaded file to be read, used to create an entry in the model, and then deleted.
Even the admin should not be able to manually add an element entering the title and description in a HTML form but only by uploading a file.
Can someone help me in a Django-way?
You can create two forms:
A form based on django.forms.Form which is used to get the file from request
A model form which is used to validate model fields and create a model object
Then you can call the second form from the first one, like this:
class MyModelForm(ModelForm):
class Meta:
model = MyModel
class FileUploadForm(forms.Form):
file = forms.FileField()
def clean_file(self):
data = self.cleaned_data["file"]
# read and parse the file, create a Python dictionary `data_dict` from it
form = MyModelForm(data_dict)
if form.is_valid():
# we don't want to put the object to the database on this step
self.instance = form.save(commit=False)
else:
# You can use more specific error message here
raise forms.ValidationError(u"The file contains invalid data.")
return data
def save(self):
# We are not overriding the `save` method here because `form.Form` does not have it.
# We just add it for convenience.
instance = getattr(self, "instance", None)
if instance:
instance.save()
return instance
def my_view(request):
form = FileUploadForm(request.POST, request.FILES)
if form.is_valid():
form.save()
else:
# display errors
You can use form wizard to achieve such tasks. The basic idea is to create two forms; one with the FileField and the other form with the title, description quantity fields.
The user views the form with FileField first. Once the user uploads the file and submits the request, you can render the other form with initial values read from the file (you can also delete the file at this step).
Regarding the admin functionality, you can read about how to integrate form wizard with admin here
I found another way of populating a model before saving it.
Instead of using pre_save, or using 2 different forms, if we are using the admin.ModelAdmin, we can simply redefine the save_model() method:
def save_model(self, request, obj, form, change):
obj.user = request.user
# populate the model
obj.save()
# post actions if needed
To achieve this you have to write some custom code. Each FileField has a connected File object. You can read the content of this file object like you would when dealing with files in Python.
There are of course different locations you could do that. You can overwrite the forms/models save method which contains the FileField. If you have model you could use pre_save/post_save signals as well.
Related
I have created a webpage where a candidate can apply for jobs. There, they can list different previous experiences. I am handling all experience details by concatenating them in my models and splitting them while fetching. how should I handle files in such a case?
My Applicant model has the FileField called company_doc. Can it also somehow take multiple files so that I can retrieve them via some indexing? is that possible?
There is, at the moment of writing, no FileField for a model that can store multiple files. Usually this is modelled with an extra model, for example CompanyDoc that has a FileField and a ForeignKey to the Applicant model, so:
class Applicant(models.Model):
# …
pass
class CompanyDoc(models.Model):
applicant = models.ForeignKey(
Applicant, on_delete=models.CASCADE
)
file = models.FileField(upload_to='company_doc/')
You can then construct a form that can upload multiple files with:
from django import forms
class CompanyDocsForm(forms.Form):
files = forms.FileField(
widget=forms.ClearableFileInput(attrs={'multiple': True})
)
and then process this with:
def some_view(request, applicant_id):
if request.method == 'POST':
form = CompanyDocForm(request.POST, request.FILES)
if form.is_valid():
data = [
CompanyDoc(file=file, applicant_id=applicant_id)
for file in request.FILES.getlist('files')
]
CompanyDoc.objects.bulk_create(data)
else:
# …
pass
else:
# …
pass
# …
For more information, see the Uploading multiple files section of the documentation.
In a existing form I use a FileField to attach differents type of files .txt, .pdf, .png, .jpg, etc and work fine but now I need that field to accept several files, so I use the propertie multiple for the input to accept more of one files but when is stored in my database only stored the path for the first selected file and in the media folder are only stored one file no the others, this is what I have:
forms.py
class MyForm(forms.Form):
attachment = forms.FileField(required=False,widget=forms.ClearableFileInput(attrs={'multiple': True}))
models.py
class MyFormModel(models.Model):
attachment = models.FileField(upload_to='path/', blank=True)
Is posible to store in the DB all the paths separete in this way path/file1.txt,path/file2.jpg,path/file3.pdf and store the three files in the media folder? Do I need a custom FileField to procces this or the view is where I need to handle this?
EDIT: The answer #harmaahylje gives me comes in the docs but not for the versión I use 1.8 this affect the solution?
Do something like this in the forms.py:
class FileFieldForm(forms.Form):
attachment = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
Django docs have the solution https://docs.djangoproject.com/en/dev/topics/http/file-uploads/#uploading-multiple-files
In your view:
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
files = request.FILES.getlist('file_field')
if form.is_valid():
for f in files:
... # Do something with each file.
return self.form_valid(form)
else:
return self.form_invalid(form)
In Django's admin, I've seen how I can set the fields of an 'add object' form using GET variables (e.g. /admin/app/model/add?title=lol sets the 'Title' field to 'lol').
However, I want to be able to do something along the lines of /admin/app/model/add?key=18 and load default data for my fields from an instance of another model. Ideally, I'd also like to be able to do some processing on the values that I populate the form with. How do I do this?
I managed to figure it out. Thankfully, Django allows you to replace a request's GET dict (which it uses to pre-populate the admin form). The following worked for me:
class ArticleAdmin(admin.ModelAdmin):
# ...
def add_view(self, request, form_url='', extra_context=None):
source_id = request.GET.get('source', None)
if source_id is not None:
source = FeedPost.objects.get(id=source_id)
# any extra processing can go here...
g = request.GET.copy()
g.update({
'title': source.title,
'contents': source.description + u"... \n\n[" + source.url + "]",
})
request.GET = g
return super(ArticleAdmin, self).add_view(request, form_url, extra_context)
This way, I obtain the source object from a URL parameter, do what I want with them, and pre-populate the form.
You can override method add_view of ModelAdmin instance. Add getting an object there, set object's pk to None and provide that object as an instance to the form. Object with pk == None will be always inserted as a new object in the database on form's save()
I have a modelform that will either create a new model or edit an existing one - this is simple and should work, but for some reason I'm getting a new instance every time.
The scenario is this is the first step in an ecommerce order. The user must fill out some info describing the order (which is stored in the model). I create the model, save it, then redirect to the next view for the user to enter their cc info. I stick the model in the session so I don't have to do a DB lookup in the next view. There is a link in the template for the second (cc info) view that lets the user go back to the first view to edit their order.
# forms.py
class MyForm(forms.ModelForm):
class Meta:
fields = ('field1', 'field2')
model = MyModel
# views.py
def create_or_update(request):
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
m = form.save(commit=False)
# update some other fields that aren't in the form
m.field3 = 'blah'
m.field4 = 'blah'
m.save()
request.session['m'] = m
return HttpResponseRedirect(reverse('enter_cc_info'))
# invalid form, render template
...
else:
# check to see if we're coming back to edit an existing model
# this part works, I get an instance as expected
m = request.session.get('m', None)
if m:
instance = get_object_or_None(MyModel, id=m.id)
if instance:
form = MyForm(instance=instance)
else:
# can't find it in the DB, but it's in the session
form = MyForm({'field1': m.field1, 'field2': m.field2})
else:
form = MyForm()
# render the form
...
If I step through in the debugger when I go back to the view to edit an order that the form is created with the instance set to the previously created model, as expected. However, when the form is processed in the subsequent POST, it creates a new instance of the model when form.save() is called.
I believe this is because I've restricted the fields in the form, so there is nowhere in the rendered HTML to store the id (or other reference) to the existing model. However, I tried adding both a 'pk' and an 'id' field (not at the same time), but then my form doesn't render at all.
I suspect I'm making this more complicated than it needs to be, but I'm stuck at the moment and could use some feedback. Thanks in advance.
This is interesting. Here is my stab at it. Consider this line:
form = MyForm(request.POST)
Can you inspect the contents of request.POST? Specifically, check if there is any information regarding which instance of the model is being edited. You'll find that there is none. In other words, each time you save the form on POST a new instance will be created.
Why does this happen? When you create a form passing the instance=instance keyword argument you are telling the Form class to return an instance for an instance of the model. However when you render the form to the template, this information is used only to fill in the fields. That is, the information about the specific instance is lost. Naturally when you post pack there is way to connect to the old instance.
How can you prevent this? A common idiom is to use the primary key as part of the URL and look up an instance on POST. Then create the form. In your case this would mean:
def create_or_update(request, instance_id):
# ^^^^^
# URL param
if request.method == 'POST':
instance = get_object_or_None(Model, pk = instance_id)
# ^^^^^
# Look up the instance
form = MyForm(request.POST, instance = instance)
# ^^^^^^^
# pass the instance now.
if form.is_valid():
....
I'm using an Ajax code for uploading files. Django takes good care of file uploads on ModelForms. Just writing form.save() would upload any file data in the header, manage creating the folders if needed and even rename the file if a duplicate already exists. Take this ModelForm which only has one filed named file for example:
class UploadFileForm(ModelForm):
class Meta:
model = MyModel
fields = ('file',)
Since I'm using Ajax the only information I have in my view is request.FILES['file']. This is what I tried in my view:
form = UploadFileForm(initial={'file':request.FILES['file']})
if form.is_valid():
form.save()
But it returns an invalid form (file is required). I can do this using pure Python but with the power of Django where's the point in that?
form = UploadFileForm(request.FILES)
if form.is_valid():
form.save()
initial parameter let you initialize form fields, like giving a new form filed some initial data.
Here, you are getting the file data from a request.