kopia lustrzana https://github.com/jedie/PyInventory
commit
4b81a41b84
2
.flake8
2
.flake8
|
@ -2,6 +2,6 @@
|
|||
# Move to pyproject.toml after: https://gitlab.com/pycqa/flake8/-/issues/428
|
||||
#
|
||||
[flake8]
|
||||
exclude = .tox, .pytest_cache, *.egg-info, */migrations/*, volumes
|
||||
exclude = .pytest_cache, .tox, dist, htmlcov, */migrations/*, volumes
|
||||
#ignore = E402
|
||||
max-line-length = 119
|
||||
|
|
12
Makefile
12
Makefile
|
@ -43,15 +43,15 @@ update: check-poetry ## update the sources and installation
|
|||
poetry update
|
||||
|
||||
lint: ## Run code formatters and linter
|
||||
poetry run flynt -e "volumes" --fail-on-change --line_length=${MAX_LINE_LENGTH} .
|
||||
poetry run flynt -e "volumes" -e "htmlcov" --fail-on-change --line_length=${MAX_LINE_LENGTH} .
|
||||
poetry run isort --check-only .
|
||||
poetry run flake8 .
|
||||
|
||||
fix-code-style: ## Fix code formatting
|
||||
poetry run flynt -e "volumes" --line_length=${MAX_LINE_LENGTH} .
|
||||
poetry run pyupgrade --exit-zero-even-if-changed --py3-plus --py36-plus --py37-plus `find . -name "*.py" -type f ! -path "./.tox/*" ! -path "./volumes/*" 2>/dev/null`
|
||||
poetry run flynt -e "volumes" -e "htmlcov" --line_length=${MAX_LINE_LENGTH} .
|
||||
poetry run pyupgrade --exit-zero-even-if-changed --py3-plus --py36-plus --py37-plus `find . -name "*.py" -type f ! -path "./.tox/*" ! -path "./htmlcov/*" ! -path "*/volumes/*" 2>/dev/null`
|
||||
poetry run isort .
|
||||
poetry run autopep8 --exclude="volumes,migrations" --aggressive --aggressive --in-place --recursive .
|
||||
poetry run autopep8 --aggressive --aggressive --in-place --recursive .
|
||||
|
||||
tox-listenvs: check-poetry ## List all tox test environments
|
||||
poetry run tox --listenvs
|
||||
|
@ -86,8 +86,8 @@ createsuperuser: ## Create super user
|
|||
./manage.sh createsuperuser
|
||||
|
||||
messages: ## Make and compile locales message files
|
||||
./manage.sh makemessages --all --no-location --no-obsolete
|
||||
./manage.sh compilemessages
|
||||
./manage.sh makemessages --all --no-location --no-obsolete --ignore=htmlcov --ignore=.tox --ignore=volumes
|
||||
./manage.sh compilemessages -v 0
|
||||
|
||||
##############################################################################
|
||||
|
||||
|
|
|
@ -94,7 +94,7 @@ build-backend = "poetry.masonry.api"
|
|||
[tool.autopep8]
|
||||
# https://github.com/hhatto/autopep8#pyprojecttoml
|
||||
max_line_length = 120
|
||||
exclude = "*/migrations/*"
|
||||
exclude="*/htmlcov/*,*/migrations/*,*/volumes/*"
|
||||
|
||||
|
||||
[tool.isort]
|
||||
|
@ -102,7 +102,7 @@ exclude = "*/migrations/*"
|
|||
atomic=true
|
||||
line_length=120
|
||||
case_sensitive=false
|
||||
skip_glob=["*/migrations/*","*/volumes/*"]
|
||||
skip_glob=["*/htmlcov/*","*/migrations/*","*/volumes/*"]
|
||||
multi_line_output=3
|
||||
include_trailing_comma=true
|
||||
known_first_party=["inventory","inventory_project","inventory_tests"]
|
||||
|
@ -116,7 +116,7 @@ lines_after_imports=2
|
|||
# https://docs.pytest.org/en/latest/customize.html#pyproject-toml
|
||||
minversion = "6.0"
|
||||
DJANGO_SETTINGS_MODULE="inventory_project.settings.tests"
|
||||
norecursedirs = ".* .git __pycache__ coverage* dist volumes"
|
||||
norecursedirs = ".* .git __pycache__ coverage* dist htmlcov volumes"
|
||||
# sometimes helpfull "addopts" arguments:
|
||||
# -vv
|
||||
# --verbose
|
||||
|
|
|
@ -3,6 +3,7 @@ from adminsortable2.admin import SortableInlineAdminMixin
|
|||
from django.contrib import admin
|
||||
from django.contrib.admin.views.main import ChangeList
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from import_export.admin import ImportExportMixin
|
||||
from import_export.resources import ModelResource
|
||||
|
@ -10,12 +11,10 @@ from import_export.resources import ModelResource
|
|||
from inventory.admin.base import BaseUserAdmin
|
||||
from inventory.forms import ItemModelModelForm
|
||||
from inventory.models import ItemLinkModel, ItemModel
|
||||
from inventory.models.item import ItemImageModel
|
||||
|
||||
|
||||
class ItemLinkModelInline(SortableInlineAdminMixin, admin.TabularInline):
|
||||
model = ItemLinkModel
|
||||
extra = 1
|
||||
|
||||
class UserInlineMixin:
|
||||
def get_queryset(self, request):
|
||||
qs = super().get_queryset(request)
|
||||
|
||||
|
@ -26,8 +25,31 @@ class ItemLinkModelInline(SortableInlineAdminMixin, admin.TabularInline):
|
|||
return qs
|
||||
|
||||
|
||||
class ItemModelResource(ModelResource):
|
||||
class ItemLinkModelInline(UserInlineMixin, SortableInlineAdminMixin, admin.TabularInline):
|
||||
model = ItemLinkModel
|
||||
extra = 0
|
||||
|
||||
|
||||
class ItemImageModelInline(UserInlineMixin, SortableInlineAdminMixin, admin.TabularInline):
|
||||
def preview(self, instance):
|
||||
return format_html(
|
||||
(
|
||||
'<a href="{url}" title="{name}"'
|
||||
' target="_blank" class="image_file_input_preview">'
|
||||
'<img style="width:9em;" src="{url}"></a>'
|
||||
),
|
||||
url=instance.image.url,
|
||||
name=instance.name,
|
||||
)
|
||||
model = ItemImageModel
|
||||
extra = 0
|
||||
fields = (
|
||||
'position', 'preview', 'image', 'name', 'tags'
|
||||
)
|
||||
readonly_fields = ('preview',)
|
||||
|
||||
|
||||
class ItemModelResource(ModelResource):
|
||||
class Meta:
|
||||
model = ItemModel
|
||||
|
||||
|
@ -112,7 +134,7 @@ class ItemModelAdmin(ImportExportMixin, BaseUserAdmin):
|
|||
)}),
|
||||
)
|
||||
readonly_fields = ('id', 'create_dt', 'update_dt', 'user')
|
||||
inlines = (ItemLinkModelInline,)
|
||||
inlines = (ItemImageModelInline, ItemLinkModelInline)
|
||||
|
||||
def get_changelist(self, request, **kwargs):
|
||||
self.user = request.user
|
||||
|
|
Plik binarny nie jest wyświetlany.
|
@ -7,8 +7,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-10-26 19:24+0100\n"
|
||||
"PO-Revision-Date: 2020-10-17 18:05+0200\n"
|
||||
"POT-Creation-Date: 2020-11-15 13:09+0100\n"
|
||||
"PO-Revision-Date: 2020-11-15 13:14+0100\n"
|
||||
"Last-Translator: Jens Diemer\n"
|
||||
"Language-Team: \n"
|
||||
"Language: de\n"
|
||||
|
@ -166,6 +166,26 @@ msgstr "Link"
|
|||
msgid "ItemLinkModel.verbose_name_plural"
|
||||
msgstr "Links"
|
||||
|
||||
msgid "ItemImageModel.image.verbose_name"
|
||||
msgstr "Bild"
|
||||
|
||||
msgid "ItemImageModel.image.help_text"
|
||||
msgstr ""
|
||||
|
||||
msgid "ItemImageModel.name.verbose_name"
|
||||
msgstr "Name"
|
||||
|
||||
msgid "ItemImageModel.name.help_text"
|
||||
msgstr ""
|
||||
"Optionalen Namen passend zum Bild (Wird automatisch aus dem Dateinamen "
|
||||
"gesetzt)"
|
||||
|
||||
msgid "ItemImageModel.verbose_name"
|
||||
msgstr "Bild"
|
||||
|
||||
msgid "ItemImageModel.verbose_name_plural"
|
||||
msgstr "Bilder"
|
||||
|
||||
msgid "BaseLink.name.verbose_name"
|
||||
msgstr "Name"
|
||||
|
||||
|
@ -208,6 +228,10 @@ msgstr "Standort"
|
|||
msgid "LocationModel.verbose_name_plural"
|
||||
msgstr "Standorte"
|
||||
|
||||
#, python-format
|
||||
msgid "Image \"%(path)s\" does not exist"
|
||||
msgstr ""
|
||||
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
|
|
Plik binarny nie jest wyświetlany.
|
@ -7,8 +7,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-10-26 19:24+0100\n"
|
||||
"PO-Revision-Date: 2020-10-17 19:12+0200\n"
|
||||
"POT-Creation-Date: 2020-11-15 13:09+0100\n"
|
||||
"PO-Revision-Date: 2020-11-15 13:15+0100\n"
|
||||
"Last-Translator: Jens Diemer\n"
|
||||
"Language-Team: \n"
|
||||
"Language: en\n"
|
||||
|
@ -164,6 +164,24 @@ msgstr "Link"
|
|||
msgid "ItemLinkModel.verbose_name_plural"
|
||||
msgstr "Links"
|
||||
|
||||
msgid "ItemImageModel.image.verbose_name"
|
||||
msgstr "Image"
|
||||
|
||||
msgid "ItemImageModel.image.help_text"
|
||||
msgstr " "
|
||||
|
||||
msgid "ItemImageModel.name.verbose_name"
|
||||
msgstr "Name"
|
||||
|
||||
msgid "ItemImageModel.name.help_text"
|
||||
msgstr ""
|
||||
|
||||
msgid "ItemImageModel.verbose_name"
|
||||
msgstr "Image"
|
||||
|
||||
msgid "ItemImageModel.verbose_name_plural"
|
||||
msgstr "Images"
|
||||
|
||||
msgid "BaseLink.name.verbose_name"
|
||||
msgstr "Name"
|
||||
|
||||
|
@ -206,6 +224,10 @@ msgstr "Location"
|
|||
msgid "LocationModel.verbose_name_plural"
|
||||
msgstr "Locations"
|
||||
|
||||
#, python-format
|
||||
msgid "Image \"%(path)s\" does not exist"
|
||||
msgstr ""
|
||||
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
# Generated by Django 2.2.17 on 2020-11-15 11:09
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import inventory.models.item
|
||||
import tagulous.models.fields
|
||||
import tagulous.models.models
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('inventory', '0003_auto_20201024_1830'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Tagulous_ItemImageModel_tags',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, unique=True)),
|
||||
('slug', models.SlugField()),
|
||||
('count', models.IntegerField(default=0, help_text='Internal counter of how many times this tag is in use')),
|
||||
('protected', models.BooleanField(default=False, help_text='Will not be deleted when the count reaches 0')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('name',),
|
||||
'abstract': False,
|
||||
'unique_together': {('slug',)},
|
||||
},
|
||||
bases=(tagulous.models.models.BaseTagModel, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ItemImageModel',
|
||||
fields=[
|
||||
('create_dt', models.DateTimeField(blank=True, editable=False, help_text='ModelTimetrackingMixin.create_dt.help_text', null=True, verbose_name='ModelTimetrackingMixin.create_dt.verbose_name')),
|
||||
('update_dt', models.DateTimeField(blank=True, editable=False, help_text='ModelTimetrackingMixin.update_dt.help_text', null=True, verbose_name='ModelTimetrackingMixin.update_dt.verbose_name')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='BaseModel.id.help_text', primary_key=True, serialize=False, verbose_name='BaseModel.id.verbose_name')),
|
||||
('image', models.ImageField(help_text='ItemImageModel.image.help_text', upload_to=inventory.models.item.user_directory_path, verbose_name='ItemImageModel.image.verbose_name')),
|
||||
('name', models.CharField(blank=True, help_text='ItemImageModel.name.help_text', max_length=255, null=True, verbose_name='ItemImageModel.name.verbose_name')),
|
||||
('position', models.PositiveSmallIntegerField(default=0)),
|
||||
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.ItemModel')),
|
||||
('tags', tagulous.models.fields.TagField(_set_tag_meta=True, blank=True, case_sensitive=False, force_lowercase=False, help_text='BaseModel.tags.help_text', max_count=10, space_delimiter=False, to='inventory.Tagulous_ItemImageModel_tags', verbose_name='BaseModel.tags.verbose_name')),
|
||||
('user', models.ForeignKey(editable=False, help_text='BaseModel.user.help_text', on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='BaseModel.user.verbose_name')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'ItemImageModel.verbose_name',
|
||||
'verbose_name_plural': 'ItemImageModel.verbose_name_plural',
|
||||
'ordering': ('position',),
|
||||
},
|
||||
),
|
||||
]
|
|
@ -1,2 +1,2 @@
|
|||
from inventory.models.item import ItemLinkModel, ItemModel # noqa
|
||||
from inventory.models.item import ItemImageModel, ItemLinkModel, ItemModel # noqa
|
||||
from inventory.models.location import LocationModel # noqa
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
import tagulous.models
|
||||
from bx_py_utils.filename import clean_filename
|
||||
from ckeditor_uploader.fields import RichTextUploadingField
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from inventory.models.base import BaseModel
|
||||
from inventory.models.links import BaseLink
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ItemQuerySet(models.QuerySet):
|
||||
def sort(self):
|
||||
return self.order_by('kind', 'producer', 'name')
|
||||
|
@ -167,3 +175,58 @@ class ItemLinkModel(BaseLink):
|
|||
verbose_name = _('ItemLinkModel.verbose_name')
|
||||
verbose_name_plural = _('ItemLinkModel.verbose_name_plural')
|
||||
ordering = ('position',)
|
||||
|
||||
|
||||
def user_directory_path(instance, filename):
|
||||
"""
|
||||
Upload to /MEDIA_ROOT/...
|
||||
"""
|
||||
random_string = get_random_string()
|
||||
filename = clean_filename(filename)
|
||||
filename = f'user_{instance.user.id}/{random_string}/{filename}'
|
||||
logger.info(f'Upload filename: {filename!r}')
|
||||
return filename
|
||||
|
||||
|
||||
class ItemImageModel(BaseModel):
|
||||
"""
|
||||
Store Images to Items
|
||||
"""
|
||||
image = models.ImageField(
|
||||
upload_to=user_directory_path,
|
||||
verbose_name=_('ItemImageModel.image.verbose_name'),
|
||||
help_text=_('ItemImageModel.image.help_text')
|
||||
)
|
||||
name = models.CharField(
|
||||
null=True, blank=True,
|
||||
max_length=255,
|
||||
verbose_name=_('ItemImageModel.name.verbose_name'),
|
||||
help_text=_('ItemImageModel.name.help_text')
|
||||
)
|
||||
item = models.ForeignKey(
|
||||
ItemModel, on_delete=models.CASCADE
|
||||
)
|
||||
position = models.PositiveSmallIntegerField(
|
||||
# Note: Will be set in admin via adminsortable2
|
||||
# The JavaScript which performs the sorting is 1-indexed !
|
||||
default=0, blank=False, null=False
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name or self.image.name
|
||||
|
||||
def full_clean(self, **kwargs):
|
||||
if not self.name:
|
||||
filename = Path(self.image.name).name
|
||||
self.name = clean_filename(filename)
|
||||
|
||||
if self.user_id is None:
|
||||
# inherit owner of this link from item instance
|
||||
self.user_id = self.item.user_id
|
||||
|
||||
return super().full_clean(**kwargs)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('ItemImageModel.verbose_name')
|
||||
verbose_name_plural = _('ItemImageModel.verbose_name_plural')
|
||||
ordering = ('position',)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.contrib.auth.models import Group, Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from inventory.models import ItemLinkModel, ItemModel, LocationModel
|
||||
from inventory.models import ItemImageModel, ItemLinkModel, ItemModel, LocationModel
|
||||
|
||||
|
||||
NORMAL_USER_GROUP_NAME = 'normal user'
|
||||
|
@ -16,15 +16,21 @@ def get_permissions(*models):
|
|||
|
||||
|
||||
def get_or_create_normal_user_group():
|
||||
"""
|
||||
Will be called by:
|
||||
inventory.signals.post_migrate_callback()
|
||||
"""
|
||||
return Group.objects.get_or_create(name=NORMAL_USER_GROUP_NAME)
|
||||
|
||||
|
||||
def setup_normal_user_permissions(normal_user_group):
|
||||
"""
|
||||
Setup PyInventory "normal user" permissions
|
||||
Setup PyInventory "normal user" permissions.
|
||||
Will be called by:
|
||||
inventory.signals.post_migrate_callback()
|
||||
"""
|
||||
assert normal_user_group.name == NORMAL_USER_GROUP_NAME
|
||||
permissions = get_permissions(ItemModel, ItemLinkModel, LocationModel)
|
||||
permissions = get_permissions(ItemImageModel, ItemLinkModel, ItemModel, LocationModel)
|
||||
existing_permissions = normal_user_group.permissions.all()
|
||||
|
||||
if set(permissions) != set(existing_permissions):
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
from django.contrib.auth.models import User
|
||||
from model_bakery import baker
|
||||
|
||||
from inventory.permissions import get_or_create_normal_user_group
|
||||
|
||||
|
||||
def get_normal_pyinventory_user(**baker_kwargs):
|
||||
pyinventory_user_group = get_or_create_normal_user_group()[0]
|
||||
pyinventory_user = baker.make(
|
||||
User,
|
||||
is_staff=True, is_active=True, is_superuser=False,
|
||||
**baker_kwargs
|
||||
)
|
||||
pyinventory_user.groups.set([pyinventory_user_group])
|
||||
return pyinventory_user
|
|
@ -0,0 +1,63 @@
|
|||
import tempfile
|
||||
from unittest import mock
|
||||
|
||||
from django.http import FileResponse
|
||||
from django.test import TestCase, override_settings
|
||||
from model_bakery import baker
|
||||
|
||||
from inventory.models import ItemImageModel
|
||||
from inventory.tests.fixtures.users import get_normal_pyinventory_user
|
||||
|
||||
|
||||
class ItemImagesTestCase(TestCase):
|
||||
def test_basics(self):
|
||||
pyinventory_user1 = get_normal_pyinventory_user(id=1)
|
||||
pyinventory_user2 = get_normal_pyinventory_user(id=2)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir, override_settings(MEDIA_ROOT=tmpdir):
|
||||
print(tmpdir)
|
||||
|
||||
with self.assertLogs('inventory') as logs:
|
||||
with mock.patch('inventory.models.item.get_random_string', return_value='DrgCCsMrdIBJ'):
|
||||
image_instance = baker.make(
|
||||
ItemImageModel,
|
||||
user=pyinventory_user1,
|
||||
_create_files=True
|
||||
)
|
||||
assert image_instance.image is not None
|
||||
url = image_instance.image.url
|
||||
# url = f'/media/{image_instance.image}'
|
||||
assert url == '/media/user_1/DrgCCsMrdIBJ/mock_img.jpeg'
|
||||
assert logs.output == [
|
||||
"INFO:inventory.models.item:"
|
||||
"Upload filename: 'user_1/DrgCCsMrdIBJ/mock_img.jpeg'"
|
||||
]
|
||||
|
||||
# Anonymous user can't access:
|
||||
|
||||
with self.assertLogs('inventory') as logs, self.assertLogs('django'):
|
||||
response = self.client.get(url)
|
||||
assert response.status_code == 403
|
||||
assert logs.output == [
|
||||
'ERROR:inventory.views.media_files:Anonymous try to access files from: 1'
|
||||
]
|
||||
|
||||
# Wrong user should not access:
|
||||
|
||||
self.client.force_login(user=pyinventory_user2)
|
||||
|
||||
with self.assertLogs('inventory') as logs, self.assertLogs('django'):
|
||||
response = self.client.get(url)
|
||||
assert response.status_code == 403
|
||||
assert logs.output == [
|
||||
'ERROR:inventory.views.media_files:Wrong user ID: 2 is not 1'
|
||||
]
|
||||
|
||||
# The right user should access:
|
||||
|
||||
self.client.force_login(user=pyinventory_user1)
|
||||
|
||||
response = self.client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert isinstance(response, FileResponse)
|
||||
assert response.getvalue() == image_instance.image.read()
|
|
@ -0,0 +1,47 @@
|
|||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import Http404
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic.base import View
|
||||
from django.views.static import serve
|
||||
|
||||
from inventory.models import ItemImageModel
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UserMediaView(View):
|
||||
"""
|
||||
Serve MEDIA_URL files, but check the current user:
|
||||
"""
|
||||
|
||||
def get(self, request, user_id, path):
|
||||
media_path = f'user_{user_id}/{path}'
|
||||
|
||||
if not request.user.is_superuser:
|
||||
if request.user.id != user_id:
|
||||
# A user tries to access a file from a other use?
|
||||
if request.user.id is None:
|
||||
logger.error(f'Anonymous try to access files from: {user_id!r}')
|
||||
else:
|
||||
logger.error(f'Wrong user ID: {request.user.id!r} is not {user_id!r}')
|
||||
raise PermissionDenied
|
||||
|
||||
# Check if the image really exists:
|
||||
qs = ItemImageModel.objects.filter(
|
||||
user_id=request.user.id,
|
||||
image=media_path
|
||||
)
|
||||
if not qs.exists():
|
||||
raise Http404(_('Image "%(path)s" does not exist') % {'path': media_path})
|
||||
|
||||
# Send the file to the user:
|
||||
return serve(
|
||||
request,
|
||||
path=media_path,
|
||||
document_root=settings.MEDIA_ROOT,
|
||||
show_indexes=False
|
||||
)
|
|
@ -4,6 +4,8 @@ from django.contrib import admin
|
|||
from django.urls import path
|
||||
from django.views.generic import RedirectView
|
||||
|
||||
from inventory.views.media_files import UserMediaView
|
||||
|
||||
|
||||
admin.autodiscover()
|
||||
|
||||
|
@ -13,12 +15,12 @@ urlpatterns = [ # Don't use i18n_patterns() here
|
|||
url(r'^$', RedirectView.as_view(url='/admin/')),
|
||||
|
||||
path('ckeditor/', include('ckeditor_uploader.urls')), # TODO: check permissions?
|
||||
path('media/user_<int:user_id>/<path:path>', UserMediaView.as_view())
|
||||
]
|
||||
|
||||
|
||||
if settings.SERVE_FILES:
|
||||
urlpatterns += static.static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
urlpatterns += static.static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
|
||||
if settings.DEBUG:
|
||||
|
|
Ładowanie…
Reference in New Issue