Merge pull request #46 from jedie/refactor-item-attachments

NEW: Add files to items.
pull/49/head
Jens Diemer 2021-04-28 19:04:29 +02:00 zatwierdzone przez GitHub
commit 2882d29fc7
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
12 zmienionych plików z 231 dodań i 75 usunięć

Wyświetl plik

@ -155,6 +155,7 @@ Files are separated into: "/src/" and "/development/"
== history
* [[https://github.com/jedie/PyInventory/compare/v0.9.0...master|compare v0.9.0...master]] **dev**
* NEW: It's not possible to upload file(s) to item.
* Add a auto login if Django dev. server is used.
** tbc
* [[https://github.com/jedie/PyInventory/compare/v0.8.4...v0.9.0|v0.9.0 - 11.04.2021]]

Wyświetl plik

@ -218,6 +218,8 @@ history
* `compare v0.9.0...master <https://github.com/jedie/PyInventory/compare/v0.9.0...master>`_ **dev**
* NEW: It's not possible to upload file(s) to item.
* Add a auto login if Django dev. server is used.
* tbc
@ -377,4 +379,4 @@ donation
------------
``Note: this file is generated from README.creole 2021-04-28 16:53:58 with "python-creole"``
``Note: this file is generated from README.creole 2021-04-28 19:03:46 with "python-creole"``

Wyświetl plik

@ -11,7 +11,7 @@ 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
from inventory.models.item import ItemFileModel, ItemImageModel
class UserInlineMixin:
@ -49,6 +49,14 @@ class ItemImageModelInline(UserInlineMixin, SortableInlineAdminMixin, admin.Tabu
readonly_fields = ('preview',)
class ItemFileModelInline(UserInlineMixin, SortableInlineAdminMixin, admin.TabularInline):
model = ItemFileModel
extra = 0
fields = (
'position', 'file', 'name', 'tags'
)
class ItemModelResource(ModelResource):
class Meta:
model = ItemModel
@ -134,7 +142,7 @@ class ItemModelAdmin(ImportExportMixin, BaseUserAdmin):
)}),
)
readonly_fields = ('id', 'create_dt', 'update_dt', 'user')
inlines = (ItemImageModelInline, ItemLinkModelInline)
inlines = (ItemImageModelInline, ItemFileModelInline, ItemLinkModelInline)
def get_changelist(self, request, **kwargs):
self.user = request.user

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-11-15 13:09+0100\n"
"PO-Revision-Date: 2020-11-15 13:14+0100\n"
"POT-Creation-Date: 2021-04-28 18:31+0200\n"
"PO-Revision-Date: 2021-04-28 18:29+0200\n"
"Last-Translator: Jens Diemer\n"
"Language-Team: \n"
"Language: de\n"
@ -50,8 +50,8 @@ msgstr "Benutzer"
msgid "BaseModel.user.help_text"
msgstr ""
" Der Benutzer dem dieser Eintrag gehört und verwalten kann (Wird "
"automatisch gesetzt)"
"Der Benutzer dem dieser Eintrag gehört und verwalten kann (Wird automatisch "
"gesetzt)"
msgid "BaseModel.name.verbose_name"
msgstr "Name"
@ -65,6 +65,12 @@ msgstr "Tags"
msgid "BaseModel.tags.help_text"
msgstr " "
msgid "BaseItemAttachmentModel.name.verbose_name"
msgstr "Name"
msgid "BaseItemAttachmentModel.name.help_text"
msgstr "Optionalen Namen (Wird automatisch aus dem Dateinamen gesetzt)"
msgid "ItemModel.kind.verbose_name"
msgstr "Art"
@ -100,7 +106,7 @@ msgstr "Übergeordnet"
msgid "ItemModel.parent.help_text"
msgstr ""
" Eingebaut in einem anderen Gegenstand? (e.g.: Grafikkarte eingebaut in "
"Eingebaut in einem anderen Gegenstand? (e.g.: Grafikkarte eingebaut in "
"Rechner)"
msgid "ItemModel.lent_to.verbose_name"
@ -170,15 +176,7 @@ 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)"
msgstr " "
msgid "ItemImageModel.verbose_name"
msgstr "Bild"
@ -186,6 +184,18 @@ msgstr "Bild"
msgid "ItemImageModel.verbose_name_plural"
msgstr "Bilder"
msgid "ItemFileModel.file.verbose_name"
msgstr "Datei"
msgid "ItemFileModel.file.help_text"
msgstr ""
msgid "ItemFileModel.verbose_name"
msgstr "Datei"
msgid "ItemFileModel.verbose_name_plural"
msgstr "Dateien"
msgid "BaseLink.name.verbose_name"
msgstr "Name"
@ -227,16 +237,3 @@ msgstr "Standort"
msgid "LocationModel.verbose_name_plural"
msgstr "Standorte"
#, python-format
msgid "Image \"%(path)s\" does not exist"
msgstr ""
msgid "German"
msgstr ""
msgid "English"
msgstr ""
msgid "Log in"
msgstr ""

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-11-15 13:09+0100\n"
"PO-Revision-Date: 2020-11-15 13:15+0100\n"
"POT-Creation-Date: 2021-04-28 18:31+0200\n"
"PO-Revision-Date: 2021-04-28 18:30+0200\n"
"Last-Translator: Jens Diemer\n"
"Language-Team: \n"
"Language: en\n"
@ -50,7 +50,7 @@ msgstr "User"
msgid "BaseModel.user.help_text"
msgstr ""
" The user who is the owner of this entry and can manage it (will be set "
"The user who is the owner of this entry and can manage it (will be set "
"automatically)"
msgid "BaseModel.name.verbose_name"
@ -65,11 +65,17 @@ msgstr "Tags"
msgid "BaseModel.tags.help_text"
msgstr " "
msgid "BaseItemAttachmentModel.name.verbose_name"
msgstr "Name"
msgid "BaseItemAttachmentModel.name.help_text"
msgstr ""
msgid "ItemModel.kind.verbose_name"
msgstr "Kind"
msgid "ItemModel.kind.help_text"
msgstr ""
msgstr " "
msgid "ItemModel.producer.verbose_name"
msgstr "Producer"
@ -170,18 +176,24 @@ 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 "ItemFileModel.file.verbose_name"
msgstr "File"
msgid "ItemFileModel.file.help_text"
msgstr " "
msgid "ItemFileModel.verbose_name"
msgstr "File"
msgid "ItemFileModel.verbose_name_plural"
msgstr "Files"
msgid "BaseLink.name.verbose_name"
msgstr "Name"
@ -204,7 +216,7 @@ msgid "Link.page_title.verbose_name"
msgstr "Page title"
msgid "Link.page_title.help_text"
msgstr ""
msgstr " "
msgid "LocationModel.description.verbose_name"
msgstr "Description"
@ -223,16 +235,3 @@ msgstr "Location"
msgid "LocationModel.verbose_name_plural"
msgstr "Locations"
#, python-format
msgid "Image \"%(path)s\" does not exist"
msgstr ""
msgid "German"
msgstr ""
msgid "English"
msgstr ""
msgid "Log in"
msgstr ""

Wyświetl plik

@ -0,0 +1,35 @@
# Generated by Django 2.2.20 on 2021-04-28 15:44
from django.db import migrations, models
import tagulous.models.models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0005_serve_uploads_by_django_tools'),
]
operations = [
migrations.AlterField(
model_name='itemimagemodel',
name='name',
field=models.CharField(blank=True, help_text='BaseItemAttachmentModel.name.help_text', max_length=255, null=True, verbose_name='BaseItemAttachmentModel.name.verbose_name'),
),
migrations.CreateModel(
name='Tagulous_BaseItemAttachmentModel_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),
),
]

Wyświetl plik

@ -0,0 +1,55 @@
# Generated by Django 2.2.20 on 2021-04-28 15:54
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django_tools.serve_media_app.models
import tagulous.models.fields
import tagulous.models.models
import uuid
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('inventory', '0006_refactor_image_model'),
]
operations = [
migrations.CreateModel(
name='Tagulous_ItemFileModel_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='ItemFileModel',
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')),
('name', models.CharField(blank=True, help_text='BaseItemAttachmentModel.name.help_text', max_length=255, null=True, verbose_name='BaseItemAttachmentModel.name.verbose_name')),
('position', models.PositiveSmallIntegerField(default=0)),
('file', models.FileField(help_text='ItemFileModel.file.help_text', upload_to=django_tools.serve_media_app.models.user_directory_path, verbose_name='ItemFileModel.file.verbose_name')),
('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_ItemFileModel_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': 'ItemFileModel.verbose_name',
'verbose_name_plural': 'ItemFileModel.verbose_name_plural',
'ordering': ('position',),
},
),
]

Wyświetl plik

@ -43,3 +43,34 @@ class BaseModel(TimetrackingBaseModel):
class Meta:
abstract = True
class BaseItemAttachmentModel(BaseModel):
"""
Base model to store files or images to Items
"""
name = models.CharField(
null=True, blank=True,
max_length=255,
verbose_name=_('BaseItemAttachmentModel.name.verbose_name'),
help_text=_('BaseItemAttachmentModel.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
def full_clean(self, **kwargs):
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:
abstract = True

Wyświetl plik

@ -9,7 +9,7 @@ from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from django_tools.serve_media_app.models import user_directory_path
from inventory.models.base import BaseModel
from inventory.models.base import BaseItemAttachmentModel, BaseModel
from inventory.models.links import BaseLink
@ -177,45 +177,55 @@ class ItemLinkModel(BaseLink):
ordering = ('position',)
class ItemImageModel(BaseModel):
class ItemImageModel(BaseItemAttachmentModel):
"""
Store Images to Items
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):
# Set name by image filename:
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',)
class ItemFileModel(BaseItemAttachmentModel):
"""
Store files to Items
"""
file = models.FileField(
upload_to=user_directory_path,
verbose_name=_('ItemFileModel.file.verbose_name'),
help_text=_('ItemFileModel.file.help_text')
)
def __str__(self):
return self.name or self.file.name
def full_clean(self, **kwargs):
# Set name by filename:
if not self.name:
filename = Path(self.file.name).name
self.name = clean_filename(filename)
return super().full_clean(**kwargs)
class Meta:
verbose_name = _('ItemFileModel.verbose_name')
verbose_name_plural = _('ItemFileModel.verbose_name_plural')
ordering = ('position',)

Wyświetl plik

@ -44,16 +44,25 @@ class AdminTestCase(HtmlAssertionMixin, TestCase):
data={
'kind': 'kind',
'name': 'name',
'itemimagemodel_set-TOTAL_FORMS': '0',
'itemimagemodel_set-INITIAL_FORMS': '0',
'itemimagemodel_set-MIN_NUM_FORMS': '0',
'itemimagemodel_set-MAX_NUM_FORMS': '1000',
'itemimagemodel_set-__prefix__-position': '0',
'itemfilemodel_set-TOTAL_FORMS': '0',
'itemfilemodel_set-INITIAL_FORMS': '0',
'itemfilemodel_set-MIN_NUM_FORMS': '0',
'itemfilemodel_set-MAX_NUM_FORMS': '1000',
'itemfilemodel_set-__prefix__-position': '0',
'itemlinkmodel_set-TOTAL_FORMS': '0',
'itemlinkmodel_set-INITIAL_FORMS': '0',
'itemlinkmodel_set-MIN_NUM_FORMS': '0',
'itemlinkmodel_set-MAX_NUM_FORMS': '1000',
'itemlinkmodel_set-__prefix__-position': '0',
'_save': 'Save',
},
)
@ -84,6 +93,7 @@ class AdminTestCase(HtmlAssertionMixin, TestCase):
data={
'kind': 'kind',
'name': 'name',
'itemimagemodel_set-TOTAL_FORMS': '1',
'itemimagemodel_set-INITIAL_FORMS': '0',
'itemimagemodel_set-MIN_NUM_FORMS': '0',
@ -91,11 +101,19 @@ class AdminTestCase(HtmlAssertionMixin, TestCase):
'itemimagemodel_set-0-position': '0',
'itemimagemodel_set-__prefix__-position': '0',
'itemimagemodel_set-0-image': img,
'itemfilemodel_set-TOTAL_FORMS': '0',
'itemfilemodel_set-INITIAL_FORMS': '0',
'itemfilemodel_set-MIN_NUM_FORMS': '0',
'itemfilemodel_set-MAX_NUM_FORMS': '1000',
'itemfilemodel_set-__prefix__-position': '0',
'itemlinkmodel_set-TOTAL_FORMS': '0',
'itemlinkmodel_set-INITIAL_FORMS': '0',
'itemlinkmodel_set-MIN_NUM_FORMS': '0',
'itemlinkmodel_set-MAX_NUM_FORMS': '1000',
'itemlinkmodel_set-__prefix__-position': '0',
'_save': 'Save',
},
)