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

NEW: Add files to items.
test-add-files-to-item
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 == history
* [[https://github.com/jedie/PyInventory/compare/v0.9.0...master|compare v0.9.0...master]] **dev** * [[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. * Add a auto login if Django dev. server is used.
** tbc ** tbc
* [[https://github.com/jedie/PyInventory/compare/v0.8.4...v0.9.0|v0.9.0 - 11.04.2021]] * [[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** * `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. * Add a auto login if Django dev. server is used.
* tbc * 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.admin.base import BaseUserAdmin
from inventory.forms import ItemModelModelForm from inventory.forms import ItemModelModelForm
from inventory.models import ItemLinkModel, ItemModel from inventory.models import ItemLinkModel, ItemModel
from inventory.models.item import ItemImageModel from inventory.models.item import ItemFileModel, ItemImageModel
class UserInlineMixin: class UserInlineMixin:
@ -49,6 +49,14 @@ class ItemImageModelInline(UserInlineMixin, SortableInlineAdminMixin, admin.Tabu
readonly_fields = ('preview',) readonly_fields = ('preview',)
class ItemFileModelInline(UserInlineMixin, SortableInlineAdminMixin, admin.TabularInline):
model = ItemFileModel
extra = 0
fields = (
'position', 'file', 'name', 'tags'
)
class ItemModelResource(ModelResource): class ItemModelResource(ModelResource):
class Meta: class Meta:
model = ItemModel model = ItemModel
@ -134,7 +142,7 @@ class ItemModelAdmin(ImportExportMixin, BaseUserAdmin):
)}), )}),
) )
readonly_fields = ('id', 'create_dt', 'update_dt', 'user') readonly_fields = ('id', 'create_dt', 'update_dt', 'user')
inlines = (ItemImageModelInline, ItemLinkModelInline) inlines = (ItemImageModelInline, ItemFileModelInline, ItemLinkModelInline)
def get_changelist(self, request, **kwargs): def get_changelist(self, request, **kwargs):
self.user = request.user self.user = request.user

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-11-15 13:09+0100\n" "POT-Creation-Date: 2021-04-28 18:31+0200\n"
"PO-Revision-Date: 2020-11-15 13:14+0100\n" "PO-Revision-Date: 2021-04-28 18:29+0200\n"
"Last-Translator: Jens Diemer\n" "Last-Translator: Jens Diemer\n"
"Language-Team: \n" "Language-Team: \n"
"Language: de\n" "Language: de\n"
@ -50,8 +50,8 @@ msgstr "Benutzer"
msgid "BaseModel.user.help_text" msgid "BaseModel.user.help_text"
msgstr "" msgstr ""
" Der Benutzer dem dieser Eintrag gehört und verwalten kann (Wird " "Der Benutzer dem dieser Eintrag gehört und verwalten kann (Wird automatisch "
"automatisch gesetzt)" "gesetzt)"
msgid "BaseModel.name.verbose_name" msgid "BaseModel.name.verbose_name"
msgstr "Name" msgstr "Name"
@ -65,6 +65,12 @@ msgstr "Tags"
msgid "BaseModel.tags.help_text" msgid "BaseModel.tags.help_text"
msgstr " " 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" msgid "ItemModel.kind.verbose_name"
msgstr "Art" msgstr "Art"
@ -100,7 +106,7 @@ msgstr "Übergeordnet"
msgid "ItemModel.parent.help_text" msgid "ItemModel.parent.help_text"
msgstr "" msgstr ""
" Eingebaut in einem anderen Gegenstand? (e.g.: Grafikkarte eingebaut in " "Eingebaut in einem anderen Gegenstand? (e.g.: Grafikkarte eingebaut in "
"Rechner)" "Rechner)"
msgid "ItemModel.lent_to.verbose_name" msgid "ItemModel.lent_to.verbose_name"
@ -170,15 +176,7 @@ msgid "ItemImageModel.image.verbose_name"
msgstr "Bild" msgstr "Bild"
msgid "ItemImageModel.image.help_text" msgid "ItemImageModel.image.help_text"
msgstr "" 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" msgid "ItemImageModel.verbose_name"
msgstr "Bild" msgstr "Bild"
@ -186,6 +184,18 @@ msgstr "Bild"
msgid "ItemImageModel.verbose_name_plural" msgid "ItemImageModel.verbose_name_plural"
msgstr "Bilder" 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" msgid "BaseLink.name.verbose_name"
msgstr "Name" msgstr "Name"
@ -227,16 +237,3 @@ msgstr "Standort"
msgid "LocationModel.verbose_name_plural" msgid "LocationModel.verbose_name_plural"
msgstr "Standorte" 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 "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-11-15 13:09+0100\n" "POT-Creation-Date: 2021-04-28 18:31+0200\n"
"PO-Revision-Date: 2020-11-15 13:15+0100\n" "PO-Revision-Date: 2021-04-28 18:30+0200\n"
"Last-Translator: Jens Diemer\n" "Last-Translator: Jens Diemer\n"
"Language-Team: \n" "Language-Team: \n"
"Language: en\n" "Language: en\n"
@ -50,7 +50,7 @@ msgstr "User"
msgid "BaseModel.user.help_text" msgid "BaseModel.user.help_text"
msgstr "" 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)" "automatically)"
msgid "BaseModel.name.verbose_name" msgid "BaseModel.name.verbose_name"
@ -65,11 +65,17 @@ msgstr "Tags"
msgid "BaseModel.tags.help_text" msgid "BaseModel.tags.help_text"
msgstr " " msgstr " "
msgid "BaseItemAttachmentModel.name.verbose_name"
msgstr "Name"
msgid "BaseItemAttachmentModel.name.help_text"
msgstr ""
msgid "ItemModel.kind.verbose_name" msgid "ItemModel.kind.verbose_name"
msgstr "Kind" msgstr "Kind"
msgid "ItemModel.kind.help_text" msgid "ItemModel.kind.help_text"
msgstr "" msgstr " "
msgid "ItemModel.producer.verbose_name" msgid "ItemModel.producer.verbose_name"
msgstr "Producer" msgstr "Producer"
@ -170,18 +176,24 @@ msgstr "Image"
msgid "ItemImageModel.image.help_text" msgid "ItemImageModel.image.help_text"
msgstr " " msgstr " "
msgid "ItemImageModel.name.verbose_name"
msgstr "Name"
msgid "ItemImageModel.name.help_text"
msgstr ""
msgid "ItemImageModel.verbose_name" msgid "ItemImageModel.verbose_name"
msgstr "Image" msgstr "Image"
msgid "ItemImageModel.verbose_name_plural" msgid "ItemImageModel.verbose_name_plural"
msgstr "Images" 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" msgid "BaseLink.name.verbose_name"
msgstr "Name" msgstr "Name"
@ -204,7 +216,7 @@ msgid "Link.page_title.verbose_name"
msgstr "Page title" msgstr "Page title"
msgid "Link.page_title.help_text" msgid "Link.page_title.help_text"
msgstr "" msgstr " "
msgid "LocationModel.description.verbose_name" msgid "LocationModel.description.verbose_name"
msgstr "Description" msgstr "Description"
@ -223,16 +235,3 @@ msgstr "Location"
msgid "LocationModel.verbose_name_plural" msgid "LocationModel.verbose_name_plural"
msgstr "Locations" 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: class Meta:
abstract = True 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.utils.translation import ugettext_lazy as _
from django_tools.serve_media_app.models import user_directory_path 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 from inventory.models.links import BaseLink
@ -177,45 +177,55 @@ class ItemLinkModel(BaseLink):
ordering = ('position',) ordering = ('position',)
class ItemImageModel(BaseModel): class ItemImageModel(BaseItemAttachmentModel):
""" """
Store Images to Items Store images to Items
""" """
image = models.ImageField( image = models.ImageField(
upload_to=user_directory_path, upload_to=user_directory_path,
verbose_name=_('ItemImageModel.image.verbose_name'), verbose_name=_('ItemImageModel.image.verbose_name'),
help_text=_('ItemImageModel.image.help_text') 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): def __str__(self):
return self.name or self.image.name return self.name or self.image.name
def full_clean(self, **kwargs): def full_clean(self, **kwargs):
# Set name by image filename:
if not self.name: if not self.name:
filename = Path(self.image.name).name filename = Path(self.image.name).name
self.name = clean_filename(filename) 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) return super().full_clean(**kwargs)
class Meta: class Meta:
verbose_name = _('ItemImageModel.verbose_name') verbose_name = _('ItemImageModel.verbose_name')
verbose_name_plural = _('ItemImageModel.verbose_name_plural') verbose_name_plural = _('ItemImageModel.verbose_name_plural')
ordering = ('position',) 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={ data={
'kind': 'kind', 'kind': 'kind',
'name': 'name', 'name': 'name',
'itemimagemodel_set-TOTAL_FORMS': '0', 'itemimagemodel_set-TOTAL_FORMS': '0',
'itemimagemodel_set-INITIAL_FORMS': '0', 'itemimagemodel_set-INITIAL_FORMS': '0',
'itemimagemodel_set-MIN_NUM_FORMS': '0', 'itemimagemodel_set-MIN_NUM_FORMS': '0',
'itemimagemodel_set-MAX_NUM_FORMS': '1000', 'itemimagemodel_set-MAX_NUM_FORMS': '1000',
'itemimagemodel_set-__prefix__-position': '0', '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-TOTAL_FORMS': '0',
'itemlinkmodel_set-INITIAL_FORMS': '0', 'itemlinkmodel_set-INITIAL_FORMS': '0',
'itemlinkmodel_set-MIN_NUM_FORMS': '0', 'itemlinkmodel_set-MIN_NUM_FORMS': '0',
'itemlinkmodel_set-MAX_NUM_FORMS': '1000', 'itemlinkmodel_set-MAX_NUM_FORMS': '1000',
'itemlinkmodel_set-__prefix__-position': '0', 'itemlinkmodel_set-__prefix__-position': '0',
'_save': 'Save', '_save': 'Save',
}, },
) )
@ -84,6 +93,7 @@ class AdminTestCase(HtmlAssertionMixin, TestCase):
data={ data={
'kind': 'kind', 'kind': 'kind',
'name': 'name', 'name': 'name',
'itemimagemodel_set-TOTAL_FORMS': '1', 'itemimagemodel_set-TOTAL_FORMS': '1',
'itemimagemodel_set-INITIAL_FORMS': '0', 'itemimagemodel_set-INITIAL_FORMS': '0',
'itemimagemodel_set-MIN_NUM_FORMS': '0', 'itemimagemodel_set-MIN_NUM_FORMS': '0',
@ -91,11 +101,19 @@ class AdminTestCase(HtmlAssertionMixin, TestCase):
'itemimagemodel_set-0-position': '0', 'itemimagemodel_set-0-position': '0',
'itemimagemodel_set-__prefix__-position': '0', 'itemimagemodel_set-__prefix__-position': '0',
'itemimagemodel_set-0-image': img, '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-TOTAL_FORMS': '0',
'itemlinkmodel_set-INITIAL_FORMS': '0', 'itemlinkmodel_set-INITIAL_FORMS': '0',
'itemlinkmodel_set-MIN_NUM_FORMS': '0', 'itemlinkmodel_set-MIN_NUM_FORMS': '0',
'itemlinkmodel_set-MAX_NUM_FORMS': '1000', 'itemlinkmodel_set-MAX_NUM_FORMS': '1000',
'itemlinkmodel_set-__prefix__-position': '0', 'itemlinkmodel_set-__prefix__-position': '0',
'_save': 'Save', '_save': 'Save',
}, },
) )