diff --git a/README.creole b/README.creole index 5544079..576e306 100644 --- a/README.creole +++ b/README.creole @@ -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]] diff --git a/README.rst b/README.rst index 33d3eb4..c6d7534 100644 --- a/README.rst +++ b/README.rst @@ -218,6 +218,8 @@ history * `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"`` \ No newline at end of file +``Note: this file is generated from README.creole 2021-04-28 19:03:46 with "python-creole"`` \ No newline at end of file diff --git a/src/inventory/admin/item.py b/src/inventory/admin/item.py index bef40bc..c0e7708 100644 --- a/src/inventory/admin/item.py +++ b/src/inventory/admin/item.py @@ -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 diff --git a/src/inventory/locale/de/LC_MESSAGES/django.mo b/src/inventory/locale/de/LC_MESSAGES/django.mo index ad94665..ab2cd59 100644 Binary files a/src/inventory/locale/de/LC_MESSAGES/django.mo and b/src/inventory/locale/de/LC_MESSAGES/django.mo differ diff --git a/src/inventory/locale/de/LC_MESSAGES/django.po b/src/inventory/locale/de/LC_MESSAGES/django.po index 53a63ec..2c6126f 100644 --- a/src/inventory/locale/de/LC_MESSAGES/django.po +++ b/src/inventory/locale/de/LC_MESSAGES/django.po @@ -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 "" diff --git a/src/inventory/locale/en/LC_MESSAGES/django.mo b/src/inventory/locale/en/LC_MESSAGES/django.mo index 7dafe3a..6fc810e 100644 Binary files a/src/inventory/locale/en/LC_MESSAGES/django.mo and b/src/inventory/locale/en/LC_MESSAGES/django.mo differ diff --git a/src/inventory/locale/en/LC_MESSAGES/django.po b/src/inventory/locale/en/LC_MESSAGES/django.po index 79387d7..dd39656 100644 --- a/src/inventory/locale/en/LC_MESSAGES/django.po +++ b/src/inventory/locale/en/LC_MESSAGES/django.po @@ -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 "" diff --git a/src/inventory/migrations/0006_refactor_image_model.py b/src/inventory/migrations/0006_refactor_image_model.py new file mode 100644 index 0000000..e2f1409 --- /dev/null +++ b/src/inventory/migrations/0006_refactor_image_model.py @@ -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), + ), + ] diff --git a/src/inventory/migrations/0007_add_file_attachment.py b/src/inventory/migrations/0007_add_file_attachment.py new file mode 100644 index 0000000..245d86e --- /dev/null +++ b/src/inventory/migrations/0007_add_file_attachment.py @@ -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',), + }, + ), + ] diff --git a/src/inventory/models/base.py b/src/inventory/models/base.py index b3f20c5..2ca62e6 100644 --- a/src/inventory/models/base.py +++ b/src/inventory/models/base.py @@ -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 diff --git a/src/inventory/models/item.py b/src/inventory/models/item.py index 65a37c3..9335bb7 100644 --- a/src/inventory/models/item.py +++ b/src/inventory/models/item.py @@ -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',) diff --git a/src/inventory_project/tests/test_admin_item.py b/src/inventory_project/tests/test_admin_item.py index b281685..c3d5676 100644 --- a/src/inventory_project/tests/test_admin_item.py +++ b/src/inventory_project/tests/test_admin_item.py @@ -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', }, )