Skip to content

Commit ed2354d

Browse files
Merge pull request #19 from SADiLaR/feature/docs-upload-admin
Feature/docs upload admin
2 parents ece9364 + c02b243 commit ed2354d

File tree

7 files changed

+248
-1
lines changed

7 files changed

+248
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ ENV/
3131
env.bak/
3232
venv.bak/
3333
app/static_files/
34+
/app/documents/

app/app/urls.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
1616
"""
1717

18+
from django.conf import settings
19+
from django.conf.urls.static import static
1820
from django.contrib import admin
1921
from django.urls import path
2022

@@ -34,3 +36,6 @@
3436
handler403 = "app.views.error_403"
3537
handler404 = "app.views.error_404"
3638
handler500 = "app.views.error_500"
39+
40+
if settings.DEBUG:
41+
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

app/general/admin.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,57 @@
1+
import mimetypes
2+
13
from django.contrib import admin
4+
from django.forms import ModelForm, fields_for_model
25

3-
from .models import Institution, Language, Project, Subject
6+
from .models import DocumentFile, Institution, Language, Project, Subject
47

58

69
class ProjectAdminInline(admin.TabularInline):
710
model = Project
811
extra = 0
912

1013

14+
class DocumentFileForm(ModelForm):
15+
class Meta:
16+
model = DocumentFile
17+
fields = fields_for_model(DocumentFile)
18+
19+
def __init__(self, *args, **kwargs):
20+
super().__init__(*args, **kwargs)
21+
self.fields["mime_type"].widget.attrs["disabled"] = True
22+
23+
def clean(self):
24+
cleaned_data = super().clean()
25+
url = cleaned_data.get("url", "")
26+
uploaded_file = cleaned_data.get("uploaded_file", "")
27+
28+
if cleaned_data["mime_type"] is not None:
29+
cleaned_data["mime_type"] = (
30+
mimetypes.guess_type(uploaded_file.name)[0] if uploaded_file else ""
31+
)
32+
33+
if not url and not uploaded_file:
34+
self.add_error("url", "Either URL or uploaded file must be provided.")
35+
self.add_error("uploaded_file", "Either URL or uploaded file must be provided.")
36+
37+
if uploaded_file:
38+
limit = 10 * 1024 * 1024
39+
if uploaded_file.size and uploaded_file.size > limit:
40+
self.add_error("uploaded_file", "File size must not exceed 10MB.")
41+
42+
return cleaned_data
43+
44+
45+
class DocumentFileAdmin(admin.ModelAdmin):
46+
list_display = ["title", "license", "document_type", "available"]
47+
ordering = [
48+
"license",
49+
]
50+
search_fields = ["title", "license", "document_type"]
51+
52+
form = DocumentFileForm
53+
54+
1155
class ProjectAdmin(admin.ModelAdmin):
1256
inlines = [ProjectAdminInline]
1357

@@ -16,3 +60,4 @@ class ProjectAdmin(admin.ModelAdmin):
1660
admin.site.register(Institution, ProjectAdmin)
1761
admin.site.register(Language)
1862
admin.site.register(Subject)
63+
admin.site.register(DocumentFile, DocumentFileAdmin)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import django.core.validators
2+
import django.db.models.deletion
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('general', '0001_initial'),
10+
]
11+
12+
operations = [
13+
migrations.CreateModel(
14+
name='DocumentFile',
15+
fields=[
16+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
17+
('title', models.CharField(max_length=200)),
18+
('url', models.URLField(blank=True)),
19+
('uploaded_file', models.FileField(blank=True, help_text='Only PDF files are allowed.', upload_to='documents/', validators=[django.core.validators.FileExtensionValidator(['pdf'])])),
20+
('available', models.BooleanField(default=True)),
21+
('license', models.CharField(choices=[('MIT', 'MIT'), ('GNU', 'GNU'), ('Apache', 'Apache')], max_length=200)),
22+
('mime_type', models.CharField(blank=True, help_text='This input will auto-populate.', max_length=200)),
23+
('document_type', models.CharField(choices=[('Glossary', 'Glossary'), ('Translation', 'Translation')], max_length=200)),
24+
('Institution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='general.institution')),
25+
('languages', models.ManyToManyField(blank=True, to='general.language')),
26+
('subjects', models.ManyToManyField(blank=True, to='general.subject')),
27+
],
28+
),
29+
]

app/general/models.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from django.core.validators import FileExtensionValidator
12
from django.db import models
23

34

@@ -43,3 +44,33 @@ class Subject(models.Model):
4344

4445
def __str__(self):
4546
return self.name
47+
48+
49+
class DocumentFile(models.Model):
50+
file_validators = [FileExtensionValidator(["pdf"])]
51+
52+
license_choices = [("MIT", "MIT"), ("GNU", "GNU"), ("Apache", "Apache")]
53+
document_type_choices = [("Glossary", "Glossary"), ("Translation", "Translation")]
54+
55+
file_type = "pdf"
56+
57+
title = models.CharField(max_length=200)
58+
url = models.URLField(max_length=200, blank=True)
59+
uploaded_file = models.FileField(
60+
upload_to="documents/",
61+
validators=file_validators,
62+
blank=True,
63+
help_text="Only PDF files are allowed.",
64+
)
65+
available = models.BooleanField(default=True)
66+
license = models.CharField(max_length=200, choices=license_choices)
67+
mime_type = models.CharField(
68+
max_length=200, blank=True, help_text="This input will auto-populate."
69+
)
70+
document_type = models.CharField(max_length=200, choices=document_type_choices)
71+
Institution = models.ForeignKey("Institution", on_delete=models.CASCADE)
72+
subjects = models.ManyToManyField("Subject", blank=True)
73+
languages = models.ManyToManyField("Language", blank=True)
74+
75+
def __str__(self):
76+
return self.title
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import unittest
2+
from unittest.mock import Mock
3+
4+
from django.core.files.uploadedfile import SimpleUploadedFile
5+
6+
from general.admin import DocumentFileForm
7+
from general.models import Institution
8+
9+
10+
class TestDocumentFileForm(unittest.TestCase):
11+
def setUp(self):
12+
self.file_mock = Mock(spec=SimpleUploadedFile)
13+
self.file_mock.name = "test.pdf"
14+
self.file_mock.size = 5242880
15+
16+
def test_clean_without_url_and_file(self):
17+
tests_form = {
18+
"title": "Test",
19+
"license": "MIT",
20+
"document_type": "Glossary",
21+
"mime_type": "pdf",
22+
"Institution": Institution.objects.create(name="Test Institution"),
23+
"url": "",
24+
"uploaded_file": "",
25+
}
26+
27+
form = DocumentFileForm(tests_form)
28+
self.assertFalse(form.is_valid())
29+
self.assertEqual(form.errors["url"], ["Either URL or uploaded file must be provided."])
30+
self.assertEqual(
31+
form.errors["uploaded_file"], ["Either URL or uploaded file must be provided."]
32+
)
33+
34+
def test_clean_without_file(self):
35+
tests_form = {
36+
"title": "Test",
37+
"license": "MIT",
38+
"document_type": "Glossary",
39+
"mime_type": "pdf",
40+
"Institution": Institution.objects.create(name="Test Institution 2"),
41+
"url": "www.example.com",
42+
"uploaded_file": "",
43+
}
44+
45+
form = DocumentFileForm(tests_form)
46+
self.assertTrue(form.is_valid())
47+
48+
def test_clean_without_url(self):
49+
tests_form = {
50+
"title": "Test",
51+
"license": "MIT",
52+
"document_type": "Glossary",
53+
"mime_type": "pdf",
54+
"Institution": Institution.objects.create(name="Test Institution 3"),
55+
"url": "",
56+
"uploaded_file": self.file_mock,
57+
}
58+
59+
form = DocumentFileForm(tests_form, files={"uploaded_file": self.file_mock})
60+
61+
self.assertTrue(form.is_valid())
62+
63+
def test_clean_with_large_file(self):
64+
self.file_mock.size = 15728640
65+
66+
tests_form = {
67+
"title": "Test",
68+
"license": "MIT",
69+
"document_type": "Glossary",
70+
"mime_type": "pdf",
71+
"Institution": Institution.objects.create(name="Test Institution 4"),
72+
"url": "",
73+
"uploaded_file": self.file_mock,
74+
}
75+
76+
form = DocumentFileForm(tests_form, files={"uploaded_file": self.file_mock})
77+
self.assertFalse(form.is_valid())
78+
self.assertIn("uploaded_file", form.errors)
79+
self.assertEqual(form.errors["uploaded_file"], ["File size must not exceed 10MB."])
80+
81+
82+
if __name__ == "__main__":
83+
unittest.main()
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import unittest
2+
3+
from django.core.files.uploadedfile import SimpleUploadedFile
4+
from django.test import TestCase
5+
6+
from general.models import DocumentFile, Institution, Language, Subject
7+
8+
9+
class DocumentFileTest(TestCase):
10+
def setUp(self):
11+
self.subject = Subject.objects.create(name="Test Subject")
12+
self.language = Language.objects.create(name="Test Language", iso_code="TL")
13+
self.institution = Institution.objects.create(name="Test Institution")
14+
15+
self.title = "Some document"
16+
self.url = "https://example.com"
17+
self.uploaded_file = SimpleUploadedFile(
18+
"example.pdf", b"file_content", content_type="application/pdf"
19+
)
20+
self.license = "MIT"
21+
self.mime_type = "pdf"
22+
self.document_type = "Glossary"
23+
self.institution = self.institution
24+
25+
self.document = DocumentFile.objects.create(
26+
title=self.title,
27+
url=self.url,
28+
uploaded_file=self.uploaded_file,
29+
license=self.license,
30+
mime_type=self.mime_type,
31+
document_type=self.document_type,
32+
Institution=self.institution,
33+
)
34+
self.document.subjects.add(self.subject)
35+
self.document.languages.add(self.language)
36+
37+
def test_document_creation(self):
38+
self.assertEqual(DocumentFile.objects.count(), 1)
39+
self.assertEqual(DocumentFile.objects.get().title, self.title)
40+
41+
def test_document_str_representation(self): # Test __str__ method
42+
self.assertEqual(str(self.document), self.title)
43+
44+
def test_document_available_by_default(self): # Test default value
45+
self.assertTrue(self.document.available)
46+
47+
def tearDown(self):
48+
if self.document.uploaded_file:
49+
self.document.uploaded_file.delete()
50+
51+
52+
if __name__ == "__main__":
53+
unittest.main()

0 commit comments

Comments
 (0)