From c5c3267bd9d39bc71224ae24aee209b15ef45d72 Mon Sep 17 00:00:00 2001
From: Arseny Sokolov
Date: Fri, 15 Jul 2022 10:42:57 +0000
Subject: [PATCH 1/5] Update deprecated ugettext_lazy to gettext_lazy
---
admin_async_upload/validators.py | 2 +-
admin_async_upload/widgets.py | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/admin_async_upload/validators.py b/admin_async_upload/validators.py
index 81b501a..ea76676 100644
--- a/admin_async_upload/validators.py
+++ b/admin_async_upload/validators.py
@@ -1,7 +1,7 @@
from admin_async_upload.storage import ResumableStorage
from os.path import splitext
from django.core.exceptions import ValidationError
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
class StorageFileValidator(object):
diff --git a/admin_async_upload/widgets.py b/admin_async_upload/widgets.py
index 3e356a7..699fe9e 100644
--- a/admin_async_upload/widgets.py
+++ b/admin_async_upload/widgets.py
@@ -5,14 +5,14 @@
from django.template import loader
from django.templatetags.static import static
from django.utils.safestring import mark_safe
-from django.utils.translation import ugettext_lazy
+from django.utils.translation import gettext_lazy
from admin_async_upload.storage import ResumableStorage
class ResumableBaseWidget(FileInput):
template_name = 'admin_resumable/admin_file_input.html'
- clear_checkbox_label = ugettext_lazy('Clear')
+ clear_checkbox_label = gettext_lazy('Clear')
def render(self, name, value, attrs=None, **kwargs):
persistent_storage = ResumableStorage().get_persistent_storage()
From e3cc7cb93d8a58fd63b36265c83d5e2d1f23d852 Mon Sep 17 00:00:00 2001
From: Arseny Sokolov
Date: Fri, 15 Jul 2022 10:53:59 +0000
Subject: [PATCH 2/5] Remove deprecated force_text function
---
admin_async_upload/storage.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/admin_async_upload/storage.py b/admin_async_upload/storage.py
index e4c9e26..ec6f35e 100644
--- a/admin_async_upload/storage.py
+++ b/admin_async_upload/storage.py
@@ -4,7 +4,7 @@
from django.core.files.storage import get_storage_class
from django.conf import settings
-from django.utils.encoding import force_text, force_str
+from django.utils.encoding import force_str
class ResumableStorage(object):
@@ -41,6 +41,6 @@ def get_persistent_storage(self, *args, **kwargs):
return storage_class(*args, **kwargs)
def full_filename(self, filename, upload_to):
- dirname = force_text(datetime.datetime.now().strftime(force_str(upload_to)))
+ dirname = force_str(datetime.datetime.now().strftime(force_str(upload_to)))
filename = posixpath.join(dirname, filename)
return self.get_persistent_storage().generate_filename(filename)
From 4d558b14051dedb60e3977630cf935876b5e3e3b Mon Sep 17 00:00:00 2001
From: Arseny Sokolov
Date: Fri, 15 Jul 2022 10:57:08 +0000
Subject: [PATCH 3/5] Update deprecated functions
---
admin_async_upload/urls.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/admin_async_upload/urls.py b/admin_async_upload/urls.py
index dbcae4d..b1d8903 100644
--- a/admin_async_upload/urls.py
+++ b/admin_async_upload/urls.py
@@ -1,7 +1,7 @@
-from django.conf.urls import url
+from django.urls import path
from . import views
urlpatterns = [
- url(r'^upload/$', views.admin_resumable, name='admin_resumable'),
+ path('upload/', views.admin_resumable, name='admin_resumable'),
]
From bef1a48f91150d19b49839082dd37251af114d0d Mon Sep 17 00:00:00 2001
From: Tuomo Mattila
Date: Tue, 28 Feb 2023 17:32:38 +0700
Subject: [PATCH 4/5] updated tests for django 3.0-4.1 and removed
pyvirtualdisplay dependency
---
.DS_Store | Bin 6148 -> 0 bytes
.travis.yml | 42 ++++++-----
setup.py | 2 +-
tests/conftest.py | 94 +++++++++++------------
tests/test_uploads.py | 172 ++++++++++++++++++++++--------------------
tests/urls.py | 6 +-
tox.ini | 16 ++--
7 files changed, 171 insertions(+), 161 deletions(-)
delete mode 100644 .DS_Store
diff --git a/.DS_Store b/.DS_Store
deleted file mode 100644
index ee8fec60d94c617c46eb4eaa41c0e2ed194885ab..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 6148
zcmeHKO>5LZ7=EYS+GJbGDuNa~1U%@WlopGtc-d~dg+i;+6npSv%{Fm25R;H(cUwwf
z5xja9z50Q_L;Vl_60iEqMoLs?G$HvEkf$^09+*d^=yPBei0IJ(iX5Y3dP&ik>dTml#~&biOaq-M
z?!FxV5+eJE7=qhIKS%tS4$<#ZN)fWbbv*|0)yfXc(hhCY2DRuuxzw6u(@ia-U0Mfo
zo3@~EXneVqnyN~BV6W3A?E;tKNIvmo;IgJ!0OycD4dFyiJ!Dfrn9ym$hrx+|Um?UUm@rMQ=ZdTTw6w;s@D|
zR|MH(PTo(F2n5;pI*-C&*sfjPNaDf^;~*39WQ->VVeBWvX3~%ST<}$J(SPU_J;brE
zH_{YI5ZRX9t}ULNtghZ&TXO34JEu#|Nxi7C=1.8',
+ 'Django>=3.0.14',
],
tests_require=[
'pytest-django',
diff --git a/tests/conftest.py b/tests/conftest.py
index 74a9f83..f89114c 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,21 +1,21 @@
import pytest
import os
from selenium import webdriver
-from pyvirtualdisplay import Display
browsers = {
- 'firefox': webdriver.Firefox,
+ "firefox": webdriver.Firefox,
#'PhantomJS': webdriver.PhantomJS,
#'chrome': webdriver.Chrome,
}
+browser_options = {"firefox": webdriver.FirefoxOptions()}
-@pytest.fixture(scope='session',
- params=browsers.keys())
+browser_options["firefox"].add_argument("--headless")
+
+
+@pytest.fixture(scope="session", params=browsers.keys())
def driver(request):
- display = Display(visible=0, size=(1024, 768))
- display.start()
- b = browsers[request.param]()
+ b = browsers[request.param](options=browser_options[request.param])
request.addfinalizer(lambda *args: b.quit())
@@ -30,66 +30,62 @@ def pytest_configure():
DEBUG=False,
DEBUG_PROPAGATE_EXCEPTIONS=True,
DATABASES={
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': ':memory:'
- }
+ "default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}
},
SITE_ID=1,
- SECRET_KEY='not very secret in tests',
+ SECRET_KEY="not very secret in tests",
USE_I18N=True,
USE_L10N=True,
- STATIC_URL='/static/',
- ROOT_URLCONF='tests.urls',
+ STATIC_URL="/static/",
+ ROOT_URLCONF="tests.urls",
TEMPLATE_LOADERS=(
- 'django.template.loaders.filesystem.Loader',
- 'django.template.loaders.app_directories.Loader',
+ "django.template.loaders.filesystem.Loader",
+ "django.template.loaders.app_directories.Loader",
),
TEMPLATES=[
{
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [
- ],
- 'APP_DIRS': True,
- 'OPTIONS': {
- 'context_processors': [
- 'django.contrib.auth.context_processors.auth',
- 'django.template.context_processors.debug',
- 'django.template.context_processors.i18n',
- 'django.template.context_processors.media',
- 'django.template.context_processors.static',
- 'django.template.context_processors.tz',
- 'django.contrib.messages.context_processors.messages',
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
+ "DIRS": [],
+ "APP_DIRS": True,
+ "OPTIONS": {
+ "context_processors": [
+ "django.contrib.auth.context_processors.auth",
+ "django.template.context_processors.debug",
+ "django.template.context_processors.i18n",
+ "django.template.context_processors.media",
+ "django.template.context_processors.static",
+ "django.template.context_processors.tz",
+ "django.contrib.messages.context_processors.messages",
],
},
},
],
- MIDDLEWARE_CLASSES=(
- 'django.middleware.common.CommonMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
+ MIDDLEWARE=(
+ "django.middleware.security.SecurityMiddleware",
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.middleware.common.CommonMiddleware",
+ "django.middleware.csrf.CsrfViewMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
+ "django.contrib.messages.middleware.MessageMiddleware",
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
),
INSTALLED_APPS=(
- 'django.contrib.admin',
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.sites',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
-
- 'admin_async_upload',
- 'tests',
+ "django.contrib.admin",
+ "django.contrib.auth",
+ "django.contrib.contenttypes",
+ "django.contrib.sessions",
+ "django.contrib.sites",
+ "django.contrib.messages",
+ "django.contrib.staticfiles",
+ "admin_async_upload",
+ "tests",
),
- PASSWORD_HASHERS=(
- 'django.contrib.auth.hashers.MD5PasswordHasher',
- ),
- MEDIA_ROOT=os.path.join(os.path.dirname(__file__), 'media')
+ PASSWORD_HASHERS=("django.contrib.auth.hashers.MD5PasswordHasher",),
+ MEDIA_ROOT=os.path.join(os.path.dirname(__file__), "media"),
)
try:
import django
+
django.setup()
except AttributeError:
pass
diff --git a/tests/test_uploads.py b/tests/test_uploads.py
index 41f1ae5..0235b0b 100644
--- a/tests/test_uploads.py
+++ b/tests/test_uploads.py
@@ -14,13 +14,13 @@
def create_test_file(file_path, size_in_megabytes):
- with open(file_path, 'wb') as bigfile:
+ with open(file_path, "wb") as bigfile:
bigfile.seek(size_in_megabytes * 1024 * 1024)
- bigfile.write(b'0')
+ bigfile.write(b"0")
def clear_uploads():
- upload_path = os.path.join(settings.MEDIA_ROOT, 'admin_uploaded')
+ upload_path = os.path.join(settings.MEDIA_ROOT, "admin_uploaded")
if not os.path.exists(upload_path):
return
for the_file in os.listdir(upload_path):
@@ -40,12 +40,15 @@ def test_fake_file_upload(admin_user, admin_client):
payload = client_module.FakePayload()
def form_value_list(key, value):
- return ['--' + client_module.BOUNDARY,
- 'Content-Disposition: form-data; name="%s"' % key,
- "",
- value]
+ return [
+ "--" + client_module.BOUNDARY,
+ 'Content-Disposition: form-data; name="%s"' % key,
+ "",
+ value,
+ ]
+
form_vals = []
- file_data = 'foo bar foo bar.'
+ file_data = "foo bar foo bar."
file_size = str(len(file_data))
form_vals += form_value_list("resumableChunkNumber", "1")
form_vals += form_value_list("resumableChunkSize", file_size)
@@ -56,29 +59,32 @@ def form_value_list(key, value):
form_vals += form_value_list("resumableTotalSize", file_size)
form_vals += form_value_list("content_type_id", str(foo_ct.id))
form_vals += form_value_list("field_name", "foo")
- payload.write('\r\n'.join(form_vals + [
- '--' + client_module.BOUNDARY,
- 'Content-Disposition: form-data; name="file"; filename=foo.bar',
- 'Content-Type: application/octet-stream',
- '',
- file_data,
- '--' + client_module.BOUNDARY + '--\r\n'
- ]))
+ payload.write(
+ "\r\n".join(
+ form_vals
+ + [
+ "--" + client_module.BOUNDARY,
+ 'Content-Disposition: form-data; name="file"; filename=foo.bar',
+ "Content-Type: application/octet-stream",
+ "",
+ file_data,
+ "--" + client_module.BOUNDARY + "--\r\n",
+ ]
+ )
+ )
r = {
- 'CONTENT_LENGTH': len(payload),
- 'CONTENT_TYPE': client_module.MULTIPART_CONTENT,
- 'PATH_INFO': "/admin_resumable/upload/",
- 'REQUEST_METHOD': 'POST',
- 'wsgi.input': payload,
+ "CONTENT_LENGTH": len(payload),
+ "CONTENT_TYPE": client_module.MULTIPART_CONTENT,
+ "PATH_INFO": "/admin_resumable/upload/",
+ "REQUEST_METHOD": "POST",
+ "wsgi.input": payload,
}
response = admin_client.request(**r)
assert response.status_code == 200
upload_filename = file_size + "_foo.bar"
- upload_path = os.path.join(settings.MEDIA_ROOT,
- upload_filename
- )
- f = open(upload_path, 'r')
+ upload_path = os.path.join(settings.MEDIA_ROOT, upload_filename)
+ f = open(upload_path, "r")
uploaded_contents = f.read()
assert file_data == uploaded_contents
@@ -91,12 +97,15 @@ def test_fake_file_upload_incomplete_chunk(admin_user, admin_client):
payload = client_module.FakePayload()
def form_value_list(key, value):
- return ['--' + client_module.BOUNDARY,
- 'Content-Disposition: form-data; name="%s"' % key,
- "",
- value]
+ return [
+ "--" + client_module.BOUNDARY,
+ 'Content-Disposition: form-data; name="%s"' % key,
+ "",
+ value,
+ ]
+
form_vals = []
- file_data = 'foo bar foo bar.'
+ file_data = "foo bar foo bar."
file_size = str(len(file_data))
form_vals += form_value_list("resumableChunkNumber", "1")
form_vals += form_value_list("resumableChunkSize", "3")
@@ -107,21 +116,26 @@ def form_value_list(key, value):
form_vals += form_value_list("resumableTotalSize", file_size)
form_vals += form_value_list("content_type_id", str(foo_ct.id))
form_vals += form_value_list("field_name", "foo")
- payload.write('\r\n'.join(form_vals + [
- '--' + client_module.BOUNDARY,
- 'Content-Disposition: form-data; name="file"; filename=foo.bar',
- 'Content-Type: application/octet-stream',
- '',
- file_data[0:1],
- # missing final boundary to simulate failure
- ]))
+ payload.write(
+ "\r\n".join(
+ form_vals
+ + [
+ "--" + client_module.BOUNDARY,
+ 'Content-Disposition: form-data; name="file"; filename=foo.bar',
+ "Content-Type: application/octet-stream",
+ "",
+ file_data[0:1],
+ # missing final boundary to simulate failure
+ ]
+ )
+ )
r = {
- 'CONTENT_LENGTH': len(payload),
- 'CONTENT_TYPE': client_module.MULTIPART_CONTENT,
- 'PATH_INFO': "/admin_resumable/admin_resumable/",
- 'REQUEST_METHOD': 'POST',
- 'wsgi.input': payload,
+ "CONTENT_LENGTH": len(payload),
+ "CONTENT_TYPE": client_module.MULTIPART_CONTENT,
+ "PATH_INFO": "/admin_resumable/admin_resumable/",
+ "REQUEST_METHOD": "POST",
+ "wsgi.input": payload,
}
try:
admin_client.request(**r)
@@ -130,21 +144,21 @@ def form_value_list(key, value):
get_url = "/admin_resumable/admin_resumable/?"
get_args = {
- 'resumableChunkNumber': '1',
- 'resumableChunkSize': '3',
- 'resumableCurrentChunkSize': '3',
- 'resumableTotalSize': file_size,
- 'resumableType': "text/plain",
- 'resumableIdentifier': file_size + "-foobar",
- 'resumableFilename': "foo.bar",
- 'resumableRelativePath': "foo.bar",
- 'content_type_id': str(foo_ct.id),
- 'field_name': "foo",
+ "resumableChunkNumber": "1",
+ "resumableChunkSize": "3",
+ "resumableCurrentChunkSize": "3",
+ "resumableTotalSize": file_size,
+ "resumableType": "text/plain",
+ "resumableIdentifier": file_size + "-foobar",
+ "resumableFilename": "foo.bar",
+ "resumableRelativePath": "foo.bar",
+ "content_type_id": str(foo_ct.id),
+ "field_name": "foo",
}
# we need a fresh client because client.request breaks things
fresh_client = client_module.Client()
- fresh_client.login(username=admin_user.username, password='password')
+ fresh_client.login(username=admin_user.username, password="password")
get_response = fresh_client.get(get_url, get_args)
# should be a 404 because we uploaded an incomplete chunk
assert get_response.status_code == 404
@@ -155,27 +169,24 @@ def test_real_file_upload(admin_user, live_server, driver):
test_file_path = "/tmp/test_small_file.bin"
create_test_file(test_file_path, 5)
- driver.get(live_server.url + '/admin/')
- driver.find_element_by_id('id_username').send_keys("admin")
- driver.find_element_by_id("id_password").send_keys("password")
- driver.find_element_by_xpath('//input[@value="Log in"]').click()
+ driver.get(live_server.url + "/admin/")
+ driver.find_element(By.ID, "id_username").send_keys("admin")
+ driver.find_element(By.ID, "id_password").send_keys("password")
+ driver.find_element(By.XPATH, '//input[@value="Log in"]').click()
driver.implicitly_wait(2)
- driver.get(live_server.url + '/admin/tests/foo/add/')
- WebDriverWait(driver, 10).until(
- EC.presence_of_element_located((By.ID, "id_bar"))
- )
- driver.find_element_by_id("id_bar").send_keys("bat")
- driver.find_element_by_id(
- 'id_foo_input_file').send_keys(test_file_path)
- status_text = driver.find_element_by_id("id_foo_uploaded_status").text
+ driver.get(live_server.url + "/admin/tests/foo/add/")
+ WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "id_bar")))
+ driver.find_element(By.ID, "id_bar").send_keys("bat")
+ driver.find_element(By.ID, "id_foo_input_file").send_keys(test_file_path)
+ status_text = driver.find_element(By.ID, "id_foo_uploaded_status").text
print("status_text", status_text)
i = 0
while i < 5:
- if "Uploaded" in status_text:
+ if "Uploaded" in driver.find_element(By.ID, "id_foo_uploaded_status").text:
return # success
time.sleep(1)
i += 1
- raise Exception # something went wrong
+ assert False, f"Status text is '{driver.find_element(By.ID, 'id_foo_uploaded_status').text}'; expected 'Uploaded"
@pytest.mark.django_db
@@ -183,24 +194,21 @@ def test_real_file_upload_with_upload_to(admin_user, live_server, driver):
test_file_path = "/tmp/test_small_file.bin"
create_test_file(test_file_path, 5)
- driver.get(live_server.url + '/admin/')
- driver.find_element_by_id('id_username').send_keys("admin")
- driver.find_element_by_id("id_password").send_keys("password")
- driver.find_element_by_xpath('//input[@value="Log in"]').click()
+ driver.get(live_server.url + "/admin/")
+ driver.find_element(By.ID, "id_username").send_keys("admin")
+ driver.find_element(By.ID, "id_password").send_keys("password")
+ driver.find_element(By.XPATH, '//input[@value="Log in"]').click()
driver.implicitly_wait(2)
- driver.get(live_server.url + '/admin/tests/foo/add/')
- WebDriverWait(driver, 10).until(
- EC.presence_of_element_located((By.ID, "id_bar"))
- )
- driver.find_element_by_id("id_bar").send_keys("bat")
- driver.find_element_by_id(
- 'id_bat_input_file').send_keys(test_file_path)
- status_text = driver.find_element_by_id("id_bat_uploaded_status").text
+ driver.get(live_server.url + "/admin/tests/foo/add/")
+ WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "id_bar")))
+ driver.find_element(By.ID, "id_bar").send_keys("bat")
+ driver.find_element(By.ID, "id_bat_input_file").send_keys(test_file_path)
+ status_text = driver.find_element(By.ID, "id_bat_uploaded_status").text
print("status_text", status_text)
i = 0
while i < 5:
- if "Uploaded" in status_text:
+ if "Uploaded" in driver.find_element(By.ID, "id_bat_uploaded_status").text:
return # success
time.sleep(1)
i += 1
- raise Exception # something went wrong
+ assert False, f"Status text is {driver.find_element(By.ID, 'id_bat_uploaded_status').text}; Expected 'Uploaded'"
diff --git a/tests/urls.py b/tests/urls.py
index 90d8cab..5cd5888 100644
--- a/tests/urls.py
+++ b/tests/urls.py
@@ -1,12 +1,12 @@
import django
-from django.conf.urls import include, url
+from django.urls import include, path
from django.contrib import admin
from django.conf import settings
urlpatterns = [
- url(r'^admin_resumable/', include('admin_async_upload.urls')),
- url(r'^admin/', include(admin.site.urls)),
+ path('admin_resumable/', include('admin_async_upload.urls')),
+ path('admin/', admin.site.urls),
]
diff --git a/tox.ini b/tox.ini
index 16b2576..7bac18f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
[tox]
envlist =
- {py27,py34,py35}-django{1.8,1.9,1.10,1.11}
+ {py36,py37,py38,py39}-django{3.0,3.1,3.2},{py38,py39,py310}-django{4.0,4.1}
[testenv]
#rsx = report all errors, -s = capture=no, -x = fail fast, --pdb for local testing http://www.linuxcertif.com/man/1/py.test/
@@ -8,10 +8,10 @@ commands = py.test -rsx -s -x
setenv =
PYTHONDONTWRITEBYTECODE=1
deps =
- django1.8: Django==1.8.14
- django1.9: Django==1.9.9
- django1.10: Django==1.10.1
- django1.11: Django==1.11
- pytest-django==3.1.2
- selenium==2.45.0
- pyvirtualdisplay
+ django3.0: Django==3.0.14
+ django3.1: Django==3.1.14
+ django3.2: Django==3.2.18
+ django4.0: Django==4.0.10
+ django4.1: Django==4.1.7
+ pytest-django==4.5.2
+ selenium==4.8.2
From b317fdcdf04652817b8bdceaedfdd4befb42e8f3 Mon Sep 17 00:00:00 2001
From: Tuomo Mattila
Date: Fri, 3 Mar 2023 23:49:19 +0700
Subject: [PATCH 5/5] support callable upload_to, hackish propagation of parent
model instance
---
admin_async_upload/files.py | 4 +++-
admin_async_upload/storage.py | 9 ++++++---
.../templates/admin_resumable/admin_file_input.html | 3 ++-
admin_async_upload/widgets.py | 3 +++
4 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/admin_async_upload/files.py b/admin_async_upload/files.py
index d284d77..64ddb4d 100644
--- a/admin_async_upload/files.py
+++ b/admin_async_upload/files.py
@@ -2,6 +2,7 @@
import fnmatch
import tempfile
+from django.contrib.contenttypes.models import ContentType
from django.core.files import File
from django.utils.functional import cached_property
@@ -39,7 +40,8 @@ def chunk_storage(self):
@property
def storage_filename(self):
- return self.resumable_storage.full_filename(self.filename, self.upload_to)
+ instance = self.field.model.objects.filter(pk=self.params.get("instance_id")).first()
+ return self.resumable_storage.full_filename(self.filename, self.upload_to, instance=instance)
@property
def upload_to(self):
diff --git a/admin_async_upload/storage.py b/admin_async_upload/storage.py
index ec6f35e..494e680 100644
--- a/admin_async_upload/storage.py
+++ b/admin_async_upload/storage.py
@@ -40,7 +40,10 @@ def get_persistent_storage(self, *args, **kwargs):
storage_class = get_storage_class(self.persistent_storage_class_name)
return storage_class(*args, **kwargs)
- def full_filename(self, filename, upload_to):
- dirname = force_str(datetime.datetime.now().strftime(force_str(upload_to)))
- filename = posixpath.join(dirname, filename)
+ def full_filename(self, filename, upload_to, instance=None):
+ if callable(upload_to):
+ filename = upload_to(instance, filename)
+ else:
+ dirname = force_str(datetime.datetime.now().strftime(force_str(upload_to)))
+ filename = posixpath.join(dirname, filename)
return self.get_persistent_storage().generate_filename(filename)
diff --git a/admin_async_upload/templates/admin_resumable/admin_file_input.html b/admin_async_upload/templates/admin_resumable/admin_file_input.html
index 68d463a..f985d75 100644
--- a/admin_async_upload/templates/admin_resumable/admin_file_input.html
+++ b/admin_async_upload/templates/admin_resumable/admin_file_input.html
@@ -30,7 +30,8 @@
query: {
csrfmiddlewaretoken: $("input[name='csrfmiddlewaretoken']").val(),
field_name: '{{ field_name }}', {# FIXME: this probably should be checked at run time for added inlines #}
- content_type_id: '{{ content_type_id }}' {# FIXME: this probably should be checked at run time for added inlines #}
+ content_type_id: '{{ content_type_id }}', {# FIXME: this probably should be checked at run time for added inlines #}
+ instance_id: '{{ instance_id }}'
},
simultaneousUploads: {{ simultaneous_uploads }}, //3 is better, 1 is used for local testing;
});
diff --git a/admin_async_upload/widgets.py b/admin_async_upload/widgets.py
index 699fe9e..b6a98ff 100644
--- a/admin_async_upload/widgets.py
+++ b/admin_async_upload/widgets.py
@@ -33,6 +33,8 @@ def render(self, name, value, attrs=None, **kwargs):
simultaneous_uploads = getattr(settings, 'ADMIN_SIMULTANEOUS_UPLOADS', 3)
content_type_id = ContentType.objects.get_for_model(self.attrs['model']).id
+ instance = self.attrs.get('instance')
+ instance_id = instance.pk if instance else None
context = {
'name': name,
@@ -42,6 +44,7 @@ def render(self, name, value, attrs=None, **kwargs):
'show_thumb': show_thumb,
'field_name': self.attrs['field_name'],
'content_type_id': content_type_id,
+ 'instance_id': instance_id,
'file_url': file_url,
'file_name': file_name,
'simultaneous_uploads': simultaneous_uploads,