Django FileField on Model, Delete File When Deleting Model and Confirmation - python

I have an upload dashboard with an upload form and a table where it shows files. In the final column are actions such as delete. But when I press delete it deletes the model in the database, but the file is still in the folder. I want the file deleted from this folder too, and it would be even nicer if I could move the deleted file to another directory something like a recycle bin so admins can see files even when they are deleted by the user who uploaded them. (The userbase is under 10 so it can be simple)
Other than that, I was wonderng if there is a way to include in the pre_delete a confirmation alert box of some sort, so that when they press delete it asks in a dialogue for confirmation.
Here is the code I have so far:
My Views one for the dashboard and one for the for deletion:
#login_required(login_url='/dashboard-login/')
def dashboard(request):
current_user = request.user
current_client = request.user.client
files = ClientUpload.objects.filter(client=current_client)
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
dz_files = request.FILES.getlist('file_upload')
for f in dz_files:
new_file = ClientUpload(client=current_client, file_upload=f)
new_file.save()
logger = logging.getLogger(__name__)
logger.info("File uploaded from " + current_client.company)
else:
logger = logging.getLogger(__name__)
logger.warning("Upload Failed")
return HttpResponseRedirect(reverse('dashboard'))
else:
form = UploadFileForm()
data = {'form': form, 'client': current_client, 'files': files}
return render_to_response('dashboard.html', data, context_instance=RequestContext(request))
def dashboard_delete(request, upload_id):
current_user = request.user
current_client = request.user.client
form = UploadFileForm()
p = ClientUpload.objects.get(pk=upload_id)
p.delete()
files = ClientUpload.objects.filter(client=current_client)
data = {'form': form, 'client': current_client, 'files': files}
return render_to_response('dashboard.html', data, context_instance=RequestContext(request))
My Model and signal:
#python_2_unicode_compatible
class ClientUpload(models.Model):
client = models.ForeignKey(Client)
created_at = models.DateTimeField(auto_now_add=True)
file_upload = models.FileField(upload_to=generate_filename)
def __str__(self):
return self.client.company
class Meta:
verbose_name_plural = _("Client Uploads")
verbose_name = _("Client Upload")
#receiver(post_delete, sender=ClientUpload)
def clientupload_delete(sender, instance, **kwargs):
if instance.file:
# Pass false so FileField doesn't save the model.
instance.file.delete(False)
else:
# log failure
My template for the file list:
{% load i18n %}
{% load staticfiles %}
{% load sasite_filters %}
<table class="table">
<tr>
<th>{% blocktrans %}Filename{% endblocktrans %}</th>
<th>{% blocktrans %}Size (Bytes){% endblocktrans %}</th>
<th>{% blocktrans %}Upload Time{% endblocktrans %}</th>
<th>{% blocktrans %}Actions{% endblocktrans %}</th>
</tr>
{% for file in files %}
{% with uploaded_file=file.file_upload %}
<tr>
<th><a href='{{ uploaded_file.url }}'>{{ uploaded_file.name|pathend}}</a></th>
<th>{{ uploaded_file.size }}</th>
<th>{{ file.created_at }}</th>
<th>Delete</th>
{% endwith %}
{% endfor %}
</tr>
</table>
My urls for the dashboard's delete:
url(r'^dashboard/delete/(?P<upload_id>\d+)$', views.dashboard_delete, name='dashboard-delete'),
Is this all I need to do to ensure the file is deleted? Did I use the signal correctly? Am I missing a step? I just want to make sure, because I'm trying to add to the signal that I want the file to be copied to a "Recycle Bin" directory before I erase it, it doesn't need a model at this point, can just be a file in the directory. But in any case I want it deleted from it's upload_to directory and moved to another directory, before I finish up deleting the model.
Is pre or post delete the correct one to be using here? And for the confirmation box for deletion? Should I use pre_delete or should I be using JavaScript for a confirmation dialogue? I am unsure. Any examples/suggestions are of big help.
Thanks in advance.

There was a typo this works fine:
#receiver(post_delete, sender=ClientUpload)
def clientupload_postdelete(sender, instance, **kwargs):
if instance.file_upload:
filename = os.path.basename(instance.file_upload.path)
client = instance.client.company
folder_path = os.path.join(settings.MEDIA_ROOT, 'uploads/Recycle/', client + '/')
if not os.path.exists(folder_path):
os.makedirs(folder_path)
if os.path.exists(os.path.join(folder_path, filename)):
filename = append_id(filename)
shutil.move(instance.file_upload.path, os.path.join(folder_path, filename))
logger = logging.getLogger(__name__)
logger.info("File %s moved to %s" % (filename, settings.MEDIA_ROOT + '/uploads/Recycle/' + client + '/'))
# Pass False so FileField doesn't save the model.
instance.file_upload.delete(False)
else:
logger = logging.getLogger(__name__)
logger.warning("Failed to find file for copying to %s." % (settings.MEDIA_ROOT + '/uploads/Recycle/'))

Related

How is the file from OneToOne Model's FileField accessed, read in views.py and passed to template?

I am developing a simple Data Visualization App, where a user can register, login upload a file and visualize its content.
I am using default User model, and a Detail Model having OneToOne relation with User Model. The Detail has 3 fields, which are:
OneToOne(User)
FileField()
TextField()
Now, I want to access the file that is saved in FileField, in views.pyand read it's content, then visualize it using Python'sNumPyandMatplotlib`, then finally show visualization results on the frontend.
I need a guidance that how should I start and approach this task? Basically, I need deep guidance in how to access and read the file in views.py?
My models.py is:
class Detail(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
file = models.FileField(verbose_name="CSV File", upload_to='csv_files/')
file_desc = models.TextField("CSV File Description")
def __str__(self):
return ("{} ({} {})".format(self.user.email, self.user.first_name, self.user.last_name))
and in views.py, I am approaching it this way:
class VisualizeAPI(View):
template_name = 'accounts/visualize.html'
def get(self, request):
msg = {'message': "Please login to view this page."}
if request.user.is_authenticated:
detail, _ = Detail.objects.get_or_create(user=request.user)
context = {'detail': detail}
return render(request, self.template_name, context)
return render(request, self.template_name, msg)
and in template, I am approaching it this way:
<body>
<h1>Visualized Details</h1>
{% if request.user.is_authenticated %}
{{ detail }}
{% else %}
<h2>{{ message }}</h2>
{% endif %}
</body>
But it is not printing the content of the file on the frontend.
I will be glad for proper approach and guidance provided.
You need to parse the CSV file in your view and then pass it to your template.
import csv
from io import StringIO
class VisualizeAPI(View):
template_name = "accounts/visualize.html"
def get(self, request):
msg = {"message": "Please login to view this page."}
if request.user.is_authenticated:
detail, _ = Detail.objects.get_or_create(user=request.user)
if detail.file:
# Read file from detail object
file = detail.file.read().decode("utf-8")
# Parse file as csv
csv_data = csv.reader(StringIO(file), delimiter=",")
else:
csv_data = None
context = {
"detail": detail,
"csv_data": csv_data,
}
return render(request, self.template_name, context)
return render(request, self.template_name, msg)
Then you can print each row of your csv file in your template.
<body>
<h1>Visualized Details</h1>
{% if request.user.is_authenticated %}
{{ detail }}
{% for row in csv_data %}
{{ row }}
{% endfor %}
{% else %}
<h2>{{ message }}</h2>
{% endif %}
</body>

reason behind "local variable 'variable ' referenced before assignmen" ERROR

I am not much familiar with django. Was working on a learning project. So here I am getting the error which says UnboundLocalError, "local variable 'url_array' referenced before assignment" . Here's my form.py , views.py and html code. Please have a look and give me a solution.
forms.py
from django import forms
from .models import Images
class ImageForm(forms.ModelForm):
class Meta:
model = Images
fields = ('__all__')
views.py
class Upload(View):
def post(self, request):
form = ImageForm(request.POST, request.FILES)
if form.is_valid():
form.save()
imageZip=request.FILES
fs = FileSystemStorage()
url_array = []
with ZipFile(imageZip['image'], 'r') as zip:
zip.extractall()
unzip_file= zip.namelist()
for files in unzip_file:
with open(files, "rb") as file:
name = fs.save('read.jpg', file)
url_array.append(fs.url(name))
else:
form = ImageForm()
return render(request, 'toDo_app.html', context = {'form': form, 'url':url_array})
toDo_app.html
<form class="" enctype="multipart/form-data" action="/upload/" method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" >Upload</button>
</form>
<br>
{% if url %}
<p>Uploaded file: </p>
<ul>
{% for urls in url %}
<li> {{ urls }} </li>
{% endfor %}
</ul>
{% endif %}
So my error is at return render(request, 'toDo_app.html', context = {'form': form, 'url':url_array}) line.
Thanks for your time and I would be really grateful for an explanation and solution
The variable url_array is initialised within the one body of the if statement. There is a chance that the if statement is never evaluated as True and hence the url_array will be uninitialised by the time your function returns. The function will not be able to return render(...) because url_array has no value.
Just make sure that the url_array is initialised with some default value outside the if statement, in the same scope with the return statement.
assign a url_array before if block
def post(self, request):
url_array = []
form = ImageForm(request.POST, request.FILES)

Django Model Doesn't Delete, Page Just Reloads

I have an upload dashboard where it displays the users files. The files are represented by a model, and there a view for the dashboard, and a second for the delete action.
Here is the model for the file upload and the related model client:
#python_2_unicode_compatible
class ClientUpload(models.Model):
client = models.ForeignKey(Client)
created_at = models.DateTimeField(auto_now_add=True)
file_upload = models.FileField(upload_to=generate_filename)
def __str__(self):
return self.client.company
class Meta:
verbose_name_plural = _("Client Uploads")
verbose_name = _("Client Upload")
#python_2_unicode_compatible
class Client(models.Model):
user = models.OneToOneField(User)
company = models.CharField(max_length=100)
def __str__(self):
return self.company
class Meta:
verbose_name_plural = _("Clients")
verbose_name = _("Client")
permissions = (
("can_upload", _("Can upload files.")),
("can_access_uploads", _("Can access upload dashboard.")),
("is_client", _("Is a client.")),
)
Here is the url pattern for the delete view:
url(r'^dashboard/delete/(?P<upload_id>\d+)$', views.dashboard_delete, name='dashboard-delete'),
Here is the view for the dashboard and the dashboard delete action:
def dashboard_delete(request, upload_id):
p = get_object_or_404(ClientUpload, pk=upload_id)
p.delete()
return HttpResponseRedirect(reverse('dashboard'))
#login_required(login_url='/dashboard-login/')
def dashboard(request):
current_user = request.user
current_client = request.user.client
files = ClientUpload.objects.filter(client=current_client)
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
dz_files = request.FILES.getlist('file_upload')
for f in dz_files:
new_file = ClientUpload(client=current_client, file_upload=f)
new_file.save()
logger = logging.getLogger(__name__)
logger.info("File uploaded from " + current_client.company)
else:
logger = logging.getLogger(__name__)
logger.warning("Upload Failed")
return HttpResponseRedirect(reverse('dashboard'))
else:
form = UploadFileForm()
data = {'form': form, 'client': current_client, 'files': files}
return render_to_response('dashboard.html', data, context_instance=RequestContext(request))
and finally the section of the template for the file listing where the dashboard-delete view is called from:
<table class="table">
<tr>
<th>{% blocktrans %}Filename{% endblocktrans %}</th>
<th>{% blocktrans %}Size (Bytes){% endblocktrans %}</th>
<th>{% blocktrans %}Upload Time{% endblocktrans %}</th>
<th>{% blocktrans %}Actions{% endblocktrans %}</th>
</tr>
{% for file in files %}
{% with uploaded_file=file.file_upload %}
<tr>
<th>{{ uploaded_file.name|pathend }}</th>
<th>{{ uploaded_file.size }}</th>
<th>{{ file.created_at }}</th>
<th><i class="fa fa-search"></i><i class="fa fa-trash-o"></i></th>
{% endwith %}
{% endfor %}
</tr>
</table>
In actions there is an icon with an anchor to the dashboard-delete named url. I pass in the current files id. And then in the url.py file I pass it in the regex. The view then finds and deletes the file. I use get_object_or_404() and get no 404 but the page just refreshes and nothing is deleted. For some reason when I press the delete icon I get two requests to /dashboard/delete/5 (5 would be the upload_id for that file) rather than just one.
Any help would be hugely appreciated, why are my models not being deleted, the page reloads and that's it. I cannot find what is wrong. I tried using catch redirect with the dj debug toolbar but it doesn't capture the redirect back to dashboard from dashboard-delete view. Which makes no sense, ordinarily it would catch the redirect.
If you need more information or need to see more code let me know and I will post it but you should have what you need to figure it out. I've been stuck here for a week so I could really use some help to finally get his done.
I know the ID's are correct because they are in the Admin panel as the same numbers and when I hover over I see which ID each item is and they line up with the Admin panel's ID's. I also tried doing {% url 'dashboard-delete' upload_id=file.id %} and it didn't work either, I don't think it has to be a kwarg in this case anyway.
I am completely stumpped here. I've done this exact method many times and it has always worked. What am I missing?
EDIT:
After further testing, it seems the view is never even getting called which makes no sense. When I hover over the link I get the correct URL, but the view never gets called. I put a logging line at the top and it never occurs. Why would this be? What would cause it not to be called?
Noticed that it seems the view doesn't even get called at all. I cant even execute a log statement from inside on the first line. Why would this happen? I've used the same delete views before, and I even tried a CBV with DeleteView.
I had to move my /dashboard/delete above /dashboard in urlpatterns, I knew this and had forgotten. Works now. Stupid me.

Validation Error Django Form

Hey so I set up a input form for users to share projects they are working on on a website I have been developing in Django 1.5, I created model, view and Form Model, to allow users who are logged in to add links to projects they are working on.
The Model works and when I enter a text through the admin panel it creates a new object, the views all seem to work, the form loads, and seems to take input however, the Project Name field keeps throwing me a invalid input error when I attempt to fill out the form, not sure why because I am inputing a string, and the field is designated as a CharField in both the Model, and Form Model.
Model:
class Project(models.Model):
creator = models.ForeignKey(User)
project_name = models.CharField(max_length=128)
website = models.URLField(blank=True)
github = models.URLField(blank=True)
description = models.CharField(max_length=255, unique=True)
likes = models.IntegerField(default=0)
def __unicode__(self):
return self.nam
View for adding a project:
#login_required
def add_project(request):
context = RequestContext(request)
if request.method == 'POST':
form = ProjectForm(request.POST)
if form.is_valid():
form.save(commit=False)
project.creator = request.user
project.save()
return index(request)
else:
print form.errors
else:
form = ProjectForm()
return render_to_response('rango/add_project.html', {'form' : form}, context)
The Form Model:
class ProjectForm(forms.ModelForm):
project_name = forms.CharField(max_length=128, help_text="What is the name of your project?")
website = forms.CharField(max_length=200, help_text="Enter the project website:")
github = forms.CharField(max_length=200, help_text="Enter the project github:")
description = forms.CharField(widget=forms.Textarea, help_text="Description:")
likes = forms.IntegerField(widget=forms.HiddenInput(), initial=0)
class Meta:
model = Project
exclude = ('creator')
def clean(self):
cleaned_data = self.cleaned_data
website = cleaned_data.get('website')
#If Url is not empty and dont start with 'http://' prepend 'http://'
if website and not website.startswith('http://'):
website = 'http://' + website
cleaned_data['website'] = website
return cleaned_data
def clean(self):
cleaned_data = self.cleaned_data
github = cleaned_data.get('github')
#If Url is not empty and dont start with 'http://' prepend 'http://'
if github and not github.startswith('http://'):
github = 'http://' + github
cleaned_data['github'] = github
return cleaned_data
and lastly the html template:
{% extends 'rango/base.html' %}
{% block title %} Add Project {% endblock %}
{% block body_block %}
<H1>Add a new Project</H1>
<form id="project_form" method="post" action="/rango/add_project/">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{hidden}}
{% endfor %}
{% for field in form.visible_fields %}
{{field.errors}}
{{field.help_text}}
{{field}}
{% endfor %}
<input type="submit" name="submit" value="Create Project" />
</form>
{% endblock %}
The Page loads fine but when I attempt to submit i get this for project name:
Enter a valid value.
the value I entered was test for project name.
In the view function, I do not understand from where project comes from.
I would expect instead:
project = form.save(commit=False)
project.creator = request.user
project.save()

Uploading excel data into django without saving the file

I am a newbie to django and i'm desperate for help uploading and reading excel data without actually saving the data on the machine. I have written some code and taken some from the research i've done online.
These are my questions:
1. How do I Upload an excel file (without it being saved on the machine). I just want the excel file to populate some django fields and not save it.
How do I make django read the columns in the excel file and feeds into some other fields on another page. (How do I link them up?)
Most docs I've seen require me to hard-code the name of the excel file and its locations.IS there a way around this as I don't know where the user might be uploading from.
pls advice.
My views.py:
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from credit.models import Document
from credit.forms import DocumentForm
def list(request):
if request.method == 'POST':
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
newdoc = Document(docfile = request.FILES['docfile'])
newdoc.save()
return HttpResponseRedirect(reverse('credit.views.list'))
else:
form = DocumentForm()
documents = Document.objects.all()
return render_to_response('credit/list.html',
{'documents': documents, 'form': form},
context_instance=RequestContext(request)
)
My models.py is:
class Document(models.Model):
docfile = models.FileField(upload_to='documents/')
#these are the models I want the excel columns to feed into
policies = DecimalNumberField()
capital = DecimalNumberField()
inflation = DecimalNumberField()
My forms.py is:
import os
import xlrd
IMPORT_FILE_TYPES = ['.xls', ]
class DocumentForm(forms.Form):
docfile = forms.FileField(label='Select a file')
def clean(self):
data = super(DocumentForm, self).clean()
if 'docfile' not in data:
raise forms.ValidationError(_('The Excel file is required to proceed'))
docfile = data['docfile']
extension = os.path.splitext(docfile.name)[1]
if not (extension in IMPORT_FILE_TYPES):
raise forms.ValidationError(u'%s is not a valid Excel file. Please make sure your input file is an Excel file )' % docfile.name)
file_data = StringIO.StringIO()
for chunk in docfile.chunks():
file_data.write(chunk)
data['file_data'] = file_data.getvalue()
file_data.close()
try:
xlrd.open_workbook(file_contents=data['file_data'])
except xlrd.XLRDError, e:
raise forms.ValidationError(_('Unable to open XLS file: %s' % e))
return data
#i do not want to do this (specify the exact file name). Need an alternative
sh = xlrd.open_workbook('documents\june.xls').sheet_by_index(1)
inflation = open("inflation.txt", 'w')
policies= open("policies.txt", 'w')
capital= open("access_to_finance.txt", 'w')
try:
for rownum in range(sh.nrows):
inflation.write(str(rownum)+ " = " +str(sh.cell(rownum, 1).value)+"\n")
policies.write(str(rownum)+ " = " +str(sh.cell(rownum, 2).value)+"\n")
capital.write(str(rownum)+ " = " +str(sh.cell(rownum, 3).value)+"\n")
finally:
inflation.close()
policies.close()
capital.close()
Then I have a list.html file:
{% if documents %}
<ul class="nav nav-tabs">
{% for document in documents %}
<li>{{ document.docfile.name }}</li>
{% endfor %}
</ul>
{% else %}
<p>Click Upload to go to Upload page</p>
{% endif %}
<form action="{% url list %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>{{ form.non_field_errors }}</p>
<p>{{ form.docfile.label_tag }} {{ form.docfile.help_text }}</p>
<p>
{{ form.docfile.errors }}
{{ form.docfile }}
</p>
<p><input type="submit" value="Upload" /></p>
</form>
Answer for question 1:
If your upload file is less then FILE_UPLOAD_MAX_MEMORY_SIZE(2.5MB), django puts the uploaded file in memory. If your file is bigger than 2.5MB, you can change FILE_UPLOAD_MAX_MEMORY_SIZE in your settings file

Categories