Upload Files APP
This is a simple app to upload files to a server. It is built with Django using the Model View Controller (MVC) design pattern. The app is containerized using Docker and can be deployed locally or to a production server.
This app doesn't have user management. It is assumed that the user is already authenticated and authorized to upload files to the server. And it not using the Admin interface.
The repository for this app can be found here.
Running App
The home page has a form where the images are supply and a table to display the files that have been uploaded. The only allow file types are (txt, pdf and docx) and the maximum file size is 5MB.
Requirements
To run this app you will need the following:
- Docker
Installation
Local
docker-compose -f local.yml build
docker-compose -f local.yml django python manage.py makemigrations
docker-compose -f local.yml django python manage.py migrate
docker-compose -f local.yml up -d
Production
docker-compose -f production.yml build
docker-compose -f production.yml django python manage.py makemigrations
docker-compose -f production.yml django python manage.py migrate
docker-compose -f production.yml up -d
Struncture
The structure of the app a simple Django app:
.
UploadFilesApp/
├── __init__.py
├── Migration/
├ └── __init__.py
├── admin.py
├── apps.py
├── models.py
├── views.py
The models.py
file has the model for the app. The views.py
file has the logic for the app. The form.py
file has the logic of filtering and saving the files. The apps.py
file has the configuration for the app.
Model
The Model has four fields: - name: name of the file - file: the file - size: size of the file - uploaded: date of the upload
# django
from django.db import models
# utils
import re
def extension_directory_path(instance, filename):
extension_search = re.search('^.*\.([a-z]{1,})$', filename)
if extension_search:
extension = extension_search.group(1)
return 'files/{0}/{1}'.format(extension, filename)
class File(models.Model):
"""
File
This is the model for the data base
"""
name = models.CharField(max_length=200)
file = models.FileField(upload_to=extension_directory_path)
size = models.PositiveBigIntegerField()
uploaded = models.DateField(auto_now=True)
The upload of the files is done using the FileField
field. The upload_to
parameter is a function that returns the path where the file will be saved. In this case the path is files/{extension}/{filename}
. The extension_directory_path
function is used to get the extension of the file and return the path.
Views
The view render the template and handle the form. The form_valid
method is used to save the file to the database. The get_context_data
method is used to add the list of files to the context.
# Django
from django.urls import reverse_lazy
from django.views.generic import FormView
# Forms
from UploadApp.forms import FileForm
# Model
from UploadApp.models import File
class FileView(FormView):
template_name = 'files.html'
form_class = FileForm
success_url = reverse_lazy('upload_home')
def form_valid(self, form):
"""If the form is valid, save the associated model."""
self.object = form.save()
return super(FileView, self).form_valid(form)
def get_context_data(self, **kwargs):
"""adding List"""
new_kwargs = super(FileView, self).get_context_data(**kwargs)
new_kwargs['files'] = File.objects.all()
return new_kwargs
Form
The form is used to validate the file and save it to the database. The clean
method is used to validate the file size and format. The maximum size is define by the MAX_DOCUMENT_SIZE
variable and the formats by the DOCUMENT_FORMATS
variable. The save
method is used to save the file to the database.
# Django
from django import forms
from django.forms import ModelForm, ValidationError
# Model
from UploadApp.models import File
# Utils
import re
MAX_DOCUMENT_SIZE = 5*(1024**2)
DOCUMENT_FORMATS = ['pdf', 'docx', 'txt']
class FileForm(ModelForm):
"""
File Form
This form to parser the data to model and make the validation of the files.
Validations :
- size : no more than 5MB
- formats accepted : .pdf .docx .txt
"""
class Meta:
model = File
fields = ['file', 'name', 'size']
exclude = ['name', 'size']
def clean(self):
file = self.cleaned_data['file']
if file.size > MAX_DOCUMENT_SIZE:
raise ValidationError('The File has to be less than 5MB')
search_ext = re.search('^.*\.([a-z]{1,})$', file.name)
if search_ext:
if not (search_ext.group(1) in DOCUMENT_FORMATS):
raise ValidationError(
'File has to be {}'.format(DOCUMENT_FORMATS))
else:
raise ValidationError('File has no extension')
return self.cleaned_data
def save(self, commit=True):
instance = super(FileForm, self).save(commit=False)
file = self.cleaned_data['file']
instance.name = file.name
instance.size = file.size
if commit:
instance.save()
return instance
Template
The template is a simple bootstrap 5 template. The files.html
template is used to render the list of files and the form. The base.html
template is used to render the files.html
template.
The template files.html
has the form of upload files and the list of files uploaded.
File.html:
{% extends "base.html" %}
{% block head_content %}
<title> File Form </title>
{% endblock%}
{% block container %}
<div class="row justify-content-center mt-3">
<div class="col-auto">
<h1>File Form</h1>
</div>
</div>
<div class="m-5">
{% if form.errors %}
{% for field in form %}
{% for error in field.errors %}
<div class="alert alert-danger">
{{ error|escape }}
</div>
{% endfor %}
{% endfor %}
{% for error in form.non_field_errors %}
<div class="alert alert-danger">
{{ error|escape }}
</div>
{% endfor %}
{% endif %}
<form action="{% url 'upload_home' %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="mb-3 row">
<div class="col-3">
<label for="formFile" class="form-label">Only [.pdf,.txt,.doc]</label>
</div>
<div class="col-9">
<input class="form-control" type="file" id="formFile" name="file">
</div>
</div>
<div class="mb-3 row">
<div class="offset-10 col-2">
<button type="submit" class="btn btn-primary">Send</button>
</div>
</div>
</form>
</div>
<div class="row justify-content-center">
<div class="col-auto">
<h3>Files</h3>
</div>
</div>
<table class="table">
{% if files %}
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Size</th>
<th scope="col">Link</th>
</tr>
</thead>
<tbody>
{% for file in files %}
<tr>
<td>{{ file.name }}</td>
<td>{{ file.size|filesizeformat }}</td>
<td><a href="{{ file.file.url }}" >Download</a></td>
</tr>
{% endfor %}
</tbody>
{% else %}
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Size</th>
<th scope="col">Link</th>
</tr>
</thead>
<tbody>
</tbody>
{% endif %}
</table>
{% endblock %}