diff --git a/.dockerignore b/.dockerignore
index d7d4b6b..cd49c72 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -7,6 +7,7 @@ db/
*.pyc
__pycache__
django/importer/uploadeddata/*
+django/attachments/attachment/*
_scratches
exports.sh
.last_git_commit
diff --git a/.gitignore b/.gitignore
index 0c41d79..b2c78f4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ db/
*.pyc
__pycache__
django/importer/uploadeddata/*
+django/attachments/attachment/*
#_scratches
exports.sh
.last_git_commit
diff --git a/django/attachments/README.md b/django/attachments/README.md
new file mode 100644
index 0000000..e9cd013
--- /dev/null
+++ b/django/attachments/README.md
@@ -0,0 +1,3 @@
+Friendly-forked at commit https://github.com/bartTC/django-attachments/commit/d0c3e7b366691f2a57329c804b0dd002064396a6 (version 1.11) and extended for Labelbase.
+
+BSD 3-Clause "New" or "Revised" License https://github.com/bartTC/django-attachments/blob/master/LICENSE
diff --git a/django/attachments/__init__.py b/django/attachments/__init__.py
new file mode 100644
index 0000000..0bb3635
--- /dev/null
+++ b/django/attachments/__init__.py
@@ -0,0 +1 @@
+default_app_config = "attachments.apps.AttachmentsConfig"
diff --git a/django/attachments/admin.py b/django/attachments/admin.py
new file mode 100644
index 0000000..9e9672d
--- /dev/null
+++ b/django/attachments/admin.py
@@ -0,0 +1,21 @@
+from __future__ import unicode_literals
+
+from django.contrib.contenttypes.admin import GenericStackedInline
+
+
+
+from django.contrib import admin
+from .models import LabelAttachment
+from .models import Attachment
+
+class AttachmentInlines(GenericStackedInline):
+ model = Attachment
+ exclude = ()
+ extra = 1
+
+
+
+
+
+
+admin.site.register(LabelAttachment)
diff --git a/django/attachments/apps.py b/django/attachments/apps.py
new file mode 100644
index 0000000..1c56402
--- /dev/null
+++ b/django/attachments/apps.py
@@ -0,0 +1,9 @@
+from __future__ import unicode_literals
+from django.apps import AppConfig
+from django.utils.translation import gettext_lazy as _
+
+
+class AttachmentsConfig(AppConfig):
+ default_auto_field = 'django.db.models.AutoField'
+ name = "attachments"
+ verbose_name = _("Attachments")
diff --git a/django/attachments/django-attachments-1.11.zip b/django/attachments/django-attachments-1.11.zip
new file mode 100644
index 0000000..ea2e0b9
Binary files /dev/null and b/django/attachments/django-attachments-1.11.zip differ
diff --git a/django/attachments/forms.py b/django/attachments/forms.py
new file mode 100644
index 0000000..a2d84e7
--- /dev/null
+++ b/django/attachments/forms.py
@@ -0,0 +1,46 @@
+from __future__ import unicode_literals
+
+from django import forms
+from django.apps import apps
+from django.conf import settings
+from django.contrib.contenttypes.models import ContentType
+from django.template.defaultfilters import filesizeformat
+from django.utils.translation import gettext_lazy as _
+
+from .models import Attachment
+
+config = apps.get_app_config("attachments")
+
+
+def validate_max_size(data):
+ if (
+ hasattr(settings, "FILE_UPLOAD_MAX_SIZE")
+ and data.size > settings.FILE_UPLOAD_MAX_SIZE
+ ):
+ raise forms.ValidationError(
+ _("File exceeds maximum size of {size}").format(
+ size=filesizeformat(settings.FILE_UPLOAD_MAX_SIZE)
+ )
+ )
+
+
+def custom_attachment_validators(uploaded_file):
+ for validator in getattr(config, "attachment_validators", ()):
+ validator(uploaded_file)
+
+
+class AttachmentForm(forms.ModelForm):
+ attachment_file = forms.FileField(
+ label=_("Upload attachment"),
+ validators=[validate_max_size, custom_attachment_validators]
+ )
+
+ class Meta:
+ model = Attachment
+ fields = ("attachment_file",)
+
+ def save(self, request, obj, *args, **kwargs):
+ self.instance.creator = request.user
+ self.instance.content_type = ContentType.objects.get_for_model(obj)
+ self.instance.object_id = obj.pk
+ super(AttachmentForm, self).save(*args, **kwargs)
diff --git a/django/attachments/locale/README.transifex b/django/attachments/locale/README.transifex
new file mode 100644
index 0000000..90789a0
--- /dev/null
+++ b/django/attachments/locale/README.transifex
@@ -0,0 +1,11 @@
+Transifex.net Token Verification
+=================================
+
+The list of tokens bellow guarantee the respective users to be able to enable
+submission on components using the following repository url:
+
+git://github.com/bartTC/django-attachments.git
+
+Tokens:
+
+24u9WhbEZQbCGDn3ruWJ6c7YpDpSxCCf / bartTC
diff --git a/django/attachments/locale/da/LC_MESSAGES/django.mo b/django/attachments/locale/da/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..cae96e1
Binary files /dev/null and b/django/attachments/locale/da/LC_MESSAGES/django.mo differ
diff --git a/django/attachments/locale/da/LC_MESSAGES/django.po b/django/attachments/locale/da/LC_MESSAGES/django.po
new file mode 100644
index 0000000..8aeb328
--- /dev/null
+++ b/django/attachments/locale/da/LC_MESSAGES/django.po
@@ -0,0 +1,54 @@
+# django-attachments in Danish
+# django-attachments på Dansk
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Michael Lind Mortensen , 2009.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: django-attachments\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-08-27 23:52+0200\n"
+"PO-Revision-Date: 2010-04-04 00:24+0100\n"
+"Last-Translator: Martin Mahner \n"
+"Language-Team: da \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:7
+msgid "Upload attachment"
+msgstr "Upload vedhæftning"
+
+#: models.py:29
+msgid "creator"
+msgstr "skaber"
+
+#: models.py:30
+msgid "attachment"
+msgstr "vedhæftning"
+
+#: models.py:31
+msgid "created"
+msgstr "skabt"
+
+#: models.py:32
+msgid "modified"
+msgstr "ændret"
+
+#: views.py:33
+msgid "Your attachment was uploaded."
+msgstr "Din vedhæftning blev uploadet."
+
+#: views.py:51
+msgid "Your attachment was deleted."
+msgstr "Din vedhæftning blev slettet."
+
+#: templates/attachments/add_form.html:7
+msgid "Add attachment"
+msgstr "Tilføj vedhæftning"
+
+#: templates/attachments/delete_link.html:2
+msgid "Delete attachment"
+msgstr "Slet vedhæftning"
+
diff --git a/django/attachments/locale/de/LC_MESSAGES/django.mo b/django/attachments/locale/de/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..979dd37
Binary files /dev/null and b/django/attachments/locale/de/LC_MESSAGES/django.mo differ
diff --git a/django/attachments/locale/de/LC_MESSAGES/django.po b/django/attachments/locale/de/LC_MESSAGES/django.po
new file mode 100644
index 0000000..22a4f6d
--- /dev/null
+++ b/django/attachments/locale/de/LC_MESSAGES/django.po
@@ -0,0 +1,53 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: django-attachments\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-04-04 00:19+0200\n"
+"PO-Revision-Date: 2010-04-04 00:29+0100\n"
+"Last-Translator: Martin Mahner \n"
+"Language-Team: de \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:7
+msgid "Upload attachment"
+msgstr "Anhang hochladen"
+
+#: models.py:29
+msgid "creator"
+msgstr "Autor"
+
+#: models.py:30
+msgid "attachment"
+msgstr "Anhang"
+
+#: models.py:31
+msgid "created"
+msgstr "Erstellt"
+
+#: models.py:32
+msgid "modified"
+msgstr "Geändert"
+
+#: views.py:33
+msgid "Your attachment was uploaded."
+msgstr "Dein Anhang wurde hochgeladen."
+
+#: views.py:51
+msgid "Your attachment was deleted."
+msgstr "Dein Anhang wurde gelöscht."
+
+#: templates/attachments/add_form.html:7
+msgid "Add attachment"
+msgstr "Anhang hinzufügen"
+
+#: templates/attachments/delete_link.html:2
+msgid "Delete attachment"
+msgstr "Anhang löschen"
+
diff --git a/django/attachments/locale/el/LC_MESSAGES/django.mo b/django/attachments/locale/el/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..ce0354e
Binary files /dev/null and b/django/attachments/locale/el/LC_MESSAGES/django.mo differ
diff --git a/django/attachments/locale/el/LC_MESSAGES/django.po b/django/attachments/locale/el/LC_MESSAGES/django.po
new file mode 100644
index 0000000..e2452ee
--- /dev/null
+++ b/django/attachments/locale/el/LC_MESSAGES/django.po
@@ -0,0 +1,53 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Panos Laganakos , 2010.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: django-attachments\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-04-04 02:52+0300\n"
+"PO-Revision-Date: 2010-04-04 15:07+0100\n"
+"Last-Translator: Martin Mahner \n"
+"Language-Team: gr \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:7
+msgid "Upload attachment"
+msgstr "Φώρτωση συνημμένου"
+
+#: models.py:29
+msgid "creator"
+msgstr "δημιουργός"
+
+#: models.py:30
+msgid "attachment"
+msgstr "συνημμένο"
+
+#: models.py:31
+msgid "created"
+msgstr "δημιουργήθηκε"
+
+#: models.py:32
+msgid "modified"
+msgstr "τροποποιήθηκε"
+
+#: views.py:33
+msgid "Your attachment was uploaded."
+msgstr "Η επισύναψη σας ολοκληρώθηκε."
+
+#: views.py:51
+msgid "Your attachment was deleted."
+msgstr "Το συνήμμενο σας διεγράφει."
+
+#: templates/attachments/add_form.html:7
+msgid "Add attachment"
+msgstr "Προσθέστε ένα συνημμένο"
+
+#: templates/attachments/delete_link.html:2
+msgid "Delete attachment"
+msgstr "Διαγράψτε το συνημμένο"
+
diff --git a/django/attachments/locale/en/LC_MESSAGES/django.mo b/django/attachments/locale/en/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..d50a24a
Binary files /dev/null and b/django/attachments/locale/en/LC_MESSAGES/django.mo differ
diff --git a/django/attachments/locale/en/LC_MESSAGES/django.po b/django/attachments/locale/en/LC_MESSAGES/django.po
new file mode 100644
index 0000000..0b3ccbc
--- /dev/null
+++ b/django/attachments/locale/en/LC_MESSAGES/django.po
@@ -0,0 +1,53 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: django-attachments\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-04-04 00:18+0200\n"
+"PO-Revision-Date: 2010-04-04 00:28+0100\n"
+"Last-Translator: Martin Mahner \n"
+"Language-Team: en \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:7
+msgid "Upload attachment"
+msgstr "Upload attachment"
+
+#: models.py:29
+msgid "creator"
+msgstr "creator"
+
+#: models.py:30
+msgid "attachment"
+msgstr "attachment"
+
+#: models.py:31
+msgid "created"
+msgstr "created"
+
+#: models.py:32
+msgid "modified"
+msgstr "modified"
+
+#: views.py:33
+msgid "Your attachment was uploaded."
+msgstr "Your attachment was uploaded."
+
+#: views.py:51
+msgid "Your attachment was deleted."
+msgstr "Your attachment was deleted."
+
+#: templates/attachments/add_form.html:7
+msgid "Add attachment"
+msgstr "Add attachment"
+
+#: templates/attachments/delete_link.html:2
+msgid "Delete attachment"
+msgstr "Delete attachment"
+
diff --git a/django/attachments/locale/es_AR/LC_MESSAGES/django.mo b/django/attachments/locale/es_AR/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..a8b2ce5
Binary files /dev/null and b/django/attachments/locale/es_AR/LC_MESSAGES/django.mo differ
diff --git a/django/attachments/locale/es_AR/LC_MESSAGES/django.po b/django/attachments/locale/es_AR/LC_MESSAGES/django.po
new file mode 100644
index 0000000..12ec32e
--- /dev/null
+++ b/django/attachments/locale/es_AR/LC_MESSAGES/django.po
@@ -0,0 +1,54 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Gonzalo Bustos, 2015.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-05-31 17:20-0300\n"
+"PO-Revision-Date: 2015-10-11 22:04-0300\n"
+"Last-Translator: Gonzalo Bustos\n"
+"Language-Team: Spanish (Argentina)\n"
+"Language: es_AR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.6.10\n"
+
+#: forms.py:7
+msgid "Upload attachment"
+msgstr "Subir adjunto"
+
+#: models.py:32
+msgid "creator"
+msgstr "creador"
+
+#: models.py:33 models.py:38
+msgid "attachment"
+msgstr "adjunto"
+
+#: models.py:34
+msgid "created"
+msgstr "creado"
+
+#: models.py:35
+msgid "modified"
+msgstr "modificado"
+
+#: views.py:34
+msgid "Your attachment was uploaded."
+msgstr "Su adjunto fue subido."
+
+#: views.py:52
+msgid "Your attachment was deleted."
+msgstr "Su adjunto fue eliminado."
+
+#: templates/attachments/add_form.html:7
+msgid "Add attachment"
+msgstr "Agregar adjunto"
+
+#: templates/attachments/delete_link.html:2
+msgid "Delete attachment"
+msgstr "Eliminar adjunto"
diff --git a/django/attachments/locale/fi/LC_MESSAGES/django.mo b/django/attachments/locale/fi/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..599e3dd
Binary files /dev/null and b/django/attachments/locale/fi/LC_MESSAGES/django.mo differ
diff --git a/django/attachments/locale/fi/LC_MESSAGES/django.po b/django/attachments/locale/fi/LC_MESSAGES/django.po
new file mode 100644
index 0000000..a57f0de
--- /dev/null
+++ b/django/attachments/locale/fi/LC_MESSAGES/django.po
@@ -0,0 +1,54 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: django-attachments\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-06-11 15:55+0300\n"
+"PO-Revision-Date: 2016-06-11 16:03+0300\n"
+"Last-Translator: Aleksi Häkli \n"
+"Language-Team: fi \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: fi\n"
+"X-Generator: Poedit 1.8.8\n"
+
+#: forms.py:7
+msgid "Upload attachment"
+msgstr "Lähetä liite"
+
+#: models.py:29
+msgid "creator"
+msgstr "luoja"
+
+#: models.py:30
+msgid "attachment"
+msgstr "liite"
+
+#: models.py:31
+msgid "created"
+msgstr "luotu"
+
+#: models.py:32
+msgid "modified"
+msgstr "muokattu"
+
+#: views.py:33
+msgid "Your attachment was uploaded."
+msgstr "Liitteesi ladattiin palvelimelle."
+
+#: views.py:51
+msgid "Your attachment was deleted."
+msgstr "Liitteesi poistettiin palvelimelta."
+
+#: templates/attachments/add_form.html:7
+msgid "Add attachment"
+msgstr "Lisää liite"
+
+#: templates/attachments/delete_link.html:2
+msgid "Delete attachment"
+msgstr "Poista liite"
diff --git a/django/attachments/locale/fr/LC_MESSAGES/django.mo b/django/attachments/locale/fr/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..22abd79
Binary files /dev/null and b/django/attachments/locale/fr/LC_MESSAGES/django.mo differ
diff --git a/django/attachments/locale/fr/LC_MESSAGES/django.po b/django/attachments/locale/fr/LC_MESSAGES/django.po
new file mode 100644
index 0000000..4f71bc1
--- /dev/null
+++ b/django/attachments/locale/fr/LC_MESSAGES/django.po
@@ -0,0 +1,53 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: django-attachments\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-04-04 00:18+0200\n"
+"PO-Revision-Date: 2010-04-04 00:28+0100\n"
+"Last-Translator: AERT \n"
+"Language-Team: en \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:7
+msgid "Upload attachment"
+msgstr "Ajouter une pièce jointe"
+
+#: models.py:29
+msgid "creator"
+msgstr "créateur"
+
+#: models.py:30
+msgid "attachment"
+msgstr "pièce jointe"
+
+#: models.py:31
+msgid "created"
+msgstr "créé"
+
+#: models.py:32
+msgid "modified"
+msgstr "modifié"
+
+#: views.py:33
+msgid "Your attachment was uploaded."
+msgstr "Votre pièce jointe a été ajoutée."
+
+#: views.py:51
+msgid "Your attachment was deleted."
+msgstr "Votre pièce jointe a été supprimée."
+
+#: templates/attachments/add_form.html:7
+msgid "Add attachment"
+msgstr "Ajouter une pièce jointe"
+
+#: templates/attachments/delete_link.html:2
+msgid "Delete attachment"
+msgstr "Supprimer la pièce jointe"
+
diff --git a/django/attachments/locale/it/LC_MESSAGES/django.mo b/django/attachments/locale/it/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..a8b50f4
Binary files /dev/null and b/django/attachments/locale/it/LC_MESSAGES/django.mo differ
diff --git a/django/attachments/locale/it/LC_MESSAGES/django.po b/django/attachments/locale/it/LC_MESSAGES/django.po
new file mode 100644
index 0000000..b782b75
--- /dev/null
+++ b/django/attachments/locale/it/LC_MESSAGES/django.po
@@ -0,0 +1,77 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2018-05-29 16:35+0200\n"
+"PO-Revision-Date: 2018-05-29 16:39+0200\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"Last-Translator: Mario Orlandi \n"
+"Language-Team: \n"
+"X-Generator: Poedit 2.0.7\n"
+
+#: apps.py:7
+msgid "Attachments"
+msgstr "Allegati"
+
+#: forms.py:13
+#, python-brace-format
+msgid "File exceeds maximum size of {size}"
+msgstr "Il file supera le dimensioni massime di {size}"
+
+#: forms.py:18
+msgid "Upload attachment"
+msgstr "Invia allegato"
+
+#: models.py:35
+msgid "creator"
+msgstr "autore"
+
+#: models.py:36 models.py:41
+msgid "attachment"
+msgstr "allegato"
+
+#: models.py:37
+msgid "created"
+msgstr "creato il"
+
+#: models.py:38
+msgid "modified"
+msgstr "modificato il"
+
+#: models.py:42
+msgid "attachments"
+msgstr "allegati"
+
+#: models.py:45
+msgid "Can delete foreign attachments"
+msgstr "Può eliminare gli allegati esterni"
+
+#: models.py:49
+#, python-brace-format
+msgid "{username} attached {filename}"
+msgstr "{username} ha allegato {filename}"
+
+#: templates/attachments/add_form.html:8
+msgid "Add attachment"
+msgstr "Aggiungi allegato"
+
+#: templates/attachments/delete_link.html:2
+msgid "Delete attachment"
+msgstr "Elimina allegato"
+
+#: views.py:51
+msgid "Your attachment was uploaded."
+msgstr "L'allegato è stato inviato."
+
+#: views.py:75
+msgid "Your attachment was deleted."
+msgstr "L'allegato è stato eliminato."
diff --git a/django/attachments/locale/pt_BR/LC_MESSAGES/django.mo b/django/attachments/locale/pt_BR/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..f1b6105
Binary files /dev/null and b/django/attachments/locale/pt_BR/LC_MESSAGES/django.mo differ
diff --git a/django/attachments/locale/pt_BR/LC_MESSAGES/django.po b/django/attachments/locale/pt_BR/LC_MESSAGES/django.po
new file mode 100644
index 0000000..8b7af3a
--- /dev/null
+++ b/django/attachments/locale/pt_BR/LC_MESSAGES/django.po
@@ -0,0 +1,54 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-05-31 17:20-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:7
+msgid "Upload attachment"
+msgstr "Subir anexo"
+
+#: models.py:32
+msgid "creator"
+msgstr "criador"
+
+#: models.py:33 models.py:38
+msgid "attachment"
+msgstr "anexo"
+
+#: models.py:34
+msgid "created"
+msgstr "criado"
+
+#: models.py:35
+msgid "modified"
+msgstr "modificado"
+
+#: views.py:34
+msgid "Your attachment was uploaded."
+msgstr "Seu anexo foi enviado."
+
+#: views.py:52
+msgid "Your attachment was deleted."
+msgstr "Seu anexo foi excluido."
+
+#: templates/attachments/add_form.html:7
+msgid "Add attachment"
+msgstr "Adicionar anexo"
+
+#: templates/attachments/delete_link.html:2
+msgid "Delete attachment"
+msgstr "Excluir anexo"
diff --git a/django/attachments/locale/ru/LC_MESSAGES/django.po b/django/attachments/locale/ru/LC_MESSAGES/django.po
new file mode 100644
index 0000000..5c74ab9
--- /dev/null
+++ b/django/attachments/locale/ru/LC_MESSAGES/django.po
@@ -0,0 +1,61 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: django-attachments\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-04-07 23:35+0300\n"
+"PO-Revision-Date: 2017-04-07 23:26+0300:MI+ZONE\n"
+"Last-Translator: Maxim Barabanov \n"
+"Language-Team: RU \n"
+"Language: Russian\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
+"%100>=11 && n%100<=14)? 2 : 3);\n"
+
+#: .\attachments\forms.py:9
+msgid "Upload attachment"
+msgstr "Загрузить вложение"
+
+#: .\attachments\models.py:35
+msgid "creator"
+msgstr "создатель"
+
+#: .\attachments\models.py:36
+msgid "attachment"
+msgstr "вложение"
+
+#: .\attachments\models.py:37
+msgid "created"
+msgstr "создано"
+
+#: .\attachments\models.py:38
+msgid "modified"
+msgstr "изменено"
+
+#: .\attachments\models.py:43
+msgid "Can delete foreign attachments"
+msgstr "Может удалять чужие вложения"
+
+#: .\attachments\templates\attachments\add_form.html:8
+msgid "Add attachment"
+msgstr "Добавить вложение"
+
+#: .\attachments\templates\attachments\delete_link.html:2
+msgid "Delete attachment"
+msgstr "Удалить вложение"
+
+#: .\attachments\views.py:40
+msgid "Your attachment was uploaded."
+msgstr "Ваше вложение было загружено."
+
+#: .\attachments\views.py:62
+msgid "Your attachment was deleted."
+msgstr "Ваше вложение было удалено"
diff --git a/django/attachments/management/__init__.py b/django/attachments/management/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/django/attachments/management/commands/__init__.py b/django/attachments/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/django/attachments/management/commands/delete_stale_attachments.py b/django/attachments/management/commands/delete_stale_attachments.py
new file mode 100644
index 0000000..7f77ad3
--- /dev/null
+++ b/django/attachments/management/commands/delete_stale_attachments.py
@@ -0,0 +1,44 @@
+from django.core.management.base import BaseCommand
+
+from attachments.models import Attachment
+from attachments.views import remove_file_from_disk
+
+
+class Command(BaseCommand):
+ help = ("Remove attachments for which the related objects "
+ "don't exist anymore!")
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ '-y', '--yes', default='x', action='store_const', const='y',
+ dest='answer', help='Automatically confirm deletion',
+ )
+
+ def handle(self, *args, **kwargs):
+ verbose = kwargs['verbosity'] >= 1
+ answer = kwargs['answer']
+
+ # -v0 sets --yes
+ if not verbose:
+ answer = 'y'
+
+ for att in Attachment.objects.all():
+ if att.content_object is None:
+ if verbose:
+ self.stdout.write(
+ "Attachment `%s' to non-existing `%s' with PK `%s'" %
+ (att, att.content_type.model, att.object_id))
+
+ while answer not in 'yn':
+ answer = input("Do you wish to delete? [yN] ")
+ if not answer:
+ answer = 'x'
+ continue
+ answer = answer[0].lower()
+
+ if answer == 'y' :
+ remove_file_from_disk(att.attachment_file)
+ att.delete()
+
+ if verbose:
+ self.stdout.write("Deleted attachment `%s'" % att)
diff --git a/django/attachments/migrations/0001_initial.py b/django/attachments/migrations/0001_initial.py
new file mode 100644
index 0000000..3057bbd
--- /dev/null
+++ b/django/attachments/migrations/0001_initial.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.conf import settings
+import attachments.models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('contenttypes', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Attachment',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('object_id', models.PositiveIntegerField()),
+ ('attachment_file', models.FileField(upload_to=attachments.models.attachment_upload, verbose_name='attachment')),
+ ('created', models.DateTimeField(auto_now_add=True, verbose_name='created')),
+ ('modified', models.DateTimeField(auto_now=True, verbose_name='modified')),
+ ('content_type', models.ForeignKey(to='contenttypes.ContentType', on_delete=models.CASCADE)),
+ ('creator', models.ForeignKey(related_name='created_attachments', verbose_name='creator', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
+ ],
+ options={
+ 'ordering': ['-created'],
+ 'permissions': (('delete_foreign_attachments', 'Can delete foreign attachments'),),
+ },
+ bases=(models.Model,),
+ ),
+ ]
diff --git a/django/attachments/migrations/0002_auto_20180104_1247.py b/django/attachments/migrations/0002_auto_20180104_1247.py
new file mode 100644
index 0000000..707fa43
--- /dev/null
+++ b/django/attachments/migrations/0002_auto_20180104_1247.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11 on 2018-01-04 12:47
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('attachments', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='attachment',
+ options={'ordering': ['-created'], 'permissions': (('delete_foreign_attachments', 'Can delete foreign attachments'),), 'verbose_name': 'attachment', 'verbose_name_plural': 'attachments'},
+ ),
+ ]
diff --git a/django/attachments/migrations/0003_auto_20190722_1216.py b/django/attachments/migrations/0003_auto_20190722_1216.py
new file mode 100644
index 0000000..a3820bd
--- /dev/null
+++ b/django/attachments/migrations/0003_auto_20190722_1216.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.3 on 2019-07-22 12:16
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('attachments', '0002_auto_20180104_1247'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='attachment',
+ name='object_id',
+ field=models.TextField(),
+ ),
+ ]
diff --git a/django/attachments/migrations/0004_db_index.py b/django/attachments/migrations/0004_db_index.py
new file mode 100644
index 0000000..852a5b5
--- /dev/null
+++ b/django/attachments/migrations/0004_db_index.py
@@ -0,0 +1,25 @@
+# Generated by Django 3.0.9 on 2020-08-17 13:29
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('attachments', '0003_auto_20190722_1216'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='attachment',
+ name='created',
+ field=models.DateTimeField(auto_now_add=True, db_index=True,
+ verbose_name='created'),
+ ),
+ migrations.AlterField(
+ model_name='attachment',
+ name='modified',
+ field=models.DateTimeField(auto_now=True, db_index=True,
+ verbose_name='modified'),
+ ),
+ ]
diff --git a/django/attachments/migrations/0005_object_id_charfield.py b/django/attachments/migrations/0005_object_id_charfield.py
new file mode 100644
index 0000000..303e51f
--- /dev/null
+++ b/django/attachments/migrations/0005_object_id_charfield.py
@@ -0,0 +1,16 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('attachments', '0004_db_index'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='attachment',
+ name='object_id',
+ field=models.CharField(db_index=True, max_length=64),
+ ),
+ ]
diff --git a/django/attachments/migrations/0006_alter_attachment_attachment_file.py b/django/attachments/migrations/0006_alter_attachment_attachment_file.py
new file mode 100644
index 0000000..7a398af
--- /dev/null
+++ b/django/attachments/migrations/0006_alter_attachment_attachment_file.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.2.25 on 2024-04-11 14:35
+
+from django.db import migrations, models
+import uuid_upload_path.storage
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('attachments', '0005_object_id_charfield'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='attachment',
+ name='attachment_file',
+ field=models.FileField(upload_to=uuid_upload_path.storage.upload_to, verbose_name='attachment'),
+ ),
+ ]
diff --git a/django/attachments/migrations/0007_labelattachment.py b/django/attachments/migrations/0007_labelattachment.py
new file mode 100644
index 0000000..326e73f
--- /dev/null
+++ b/django/attachments/migrations/0007_labelattachment.py
@@ -0,0 +1,28 @@
+# Generated by Django 3.2.25 on 2024-04-11 15:06
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('attachments', '0006_alter_attachment_attachment_file'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='LabelAttachment',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('type_ref_hash', models.CharField(blank=True, help_text='Reflects type + ref, where type could be any bip-329 defined type', max_length=64)),
+ ('network', models.CharField(choices=[('mainnet', 'Mainnet'), ('testnet', 'Testnet')], default='mainnet', help_text="Choose the network for this labelbase's label attachement.", max_length=10)),
+ ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'unique_together': {('user', 'network', 'type_ref_hash')},
+ },
+ ),
+ ]
diff --git a/django/attachments/migrations/__init__.py b/django/attachments/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/django/attachments/models.py b/django/attachments/models.py
new file mode 100644
index 0000000..54c1d06
--- /dev/null
+++ b/django/attachments/models.py
@@ -0,0 +1,117 @@
+from __future__ import unicode_literals
+
+import os
+
+from django.conf import settings
+from django.contrib.contenttypes.fields import GenericForeignKey
+from django.contrib.contenttypes.models import ContentType
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+from six import python_2_unicode_compatible
+from uuid_upload_path import upload_to
+from django.contrib.auth.models import User
+import logging
+
+logger = logging.getLogger('labelbase')
+
+def attachment_upload(instance, filename):
+ """Stores the attachment in a "per module/appname/primary key" folder"""
+ return "attachments/{app}_{model}/{pk}/{filename}".format(
+ app=instance.content_object._meta.app_label,
+ model=instance.content_object._meta.object_name.lower(),
+ pk=instance.content_object.pk,
+ filename=filename,
+ )
+
+
+class AttachmentManager(models.Manager):
+ def attachments_for_object(self, obj):
+ object_type = ContentType.objects.get_for_model(obj)
+ print ("x attachments_for_object id {} , {} {}".format( obj.pk, object_type, object_type.id ))
+ return self.filter(content_type__pk=object_type.id, object_id=obj.pk)
+
+
+@python_2_unicode_compatible
+class Attachment(models.Model):
+ objects = AttachmentManager()
+
+ content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
+ object_id = models.CharField(db_index=True, max_length=64)
+ content_object = GenericForeignKey("content_type", "object_id")
+ creator = models.ForeignKey(
+ settings.AUTH_USER_MODEL,
+ related_name="created_attachments",
+ verbose_name=_("creator"),
+ on_delete=models.CASCADE,
+ )
+ attachment_file = models.FileField(
+ _("attachment"), upload_to=upload_to # attachment_upload
+ )
+ created = models.DateTimeField(_("created"), auto_now_add=True, db_index=True)
+ modified = models.DateTimeField(_("modified"), auto_now=True, db_index=True)
+
+ class Meta:
+ verbose_name = _("attachment")
+ verbose_name_plural = _("attachments")
+ ordering = ["-created"]
+ permissions = (
+ ("delete_foreign_attachments", _("Can delete foreign attachments")),
+ )
+
+ def __str__(self):
+ return _("{username} attached {filename}").format(
+ username=self.creator.get_username(),
+ filename=self.attachment_file.name,
+ )
+
+ @property
+ def filename(self):
+ return os.path.split(self.attachment_file.name)[1]
+
+ def attach_to(self, new_object, update_path=False):
+ """
+ Attach to a new object and possibly move the actual file on disk!
+
+ .. important::
+
+ As long as path names are valid you can continue serving
+ the files from their original path and not change it!
+ """
+ self.object_id = new_object.pk
+ self.content_type = ContentType.objects.get_for_model(new_object)
+ self.save()
+
+ if update_path:
+ old_path = self.attachment_file.path
+ self.attachment_file.name = upload_to # attachment_upload(self, self.filename)
+ self.attachment_file.save()
+
+ os.makedirs(
+ os.path.dirname(self.attachment_file.path), exist_ok=True)
+ os.rename(old_path, self.attachment_file.path)
+
+
+class LabelAttachment(models.Model):
+
+ user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
+ type_ref_hash = models.CharField(max_length=64,
+ blank=True,
+ help_text="Reflects type + ref, where type could be any bip-329 defined type")
+
+ MAINNET = 'mainnet'
+ TESTNET = 'testnet'
+
+ NETWORK_CHOICES = [
+ (MAINNET, 'Mainnet'),
+ (TESTNET, 'Testnet'),
+ ]
+
+ network = models.CharField(
+ max_length=10,
+ choices=NETWORK_CHOICES,
+ default='mainnet',
+ help_text="Choose the network for this labelbase's label attachement."
+ )
+
+ class Meta:
+ unique_together = (("user", "network", "type_ref_hash"),)
diff --git a/django/attachments/templatetags/__init__.py b/django/attachments/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/django/attachments/templatetags/attachments_tags.py b/django/attachments/templatetags/attachments_tags.py
new file mode 100644
index 0000000..b4baaa7
--- /dev/null
+++ b/django/attachments/templatetags/attachments_tags.py
@@ -0,0 +1,77 @@
+from django.template import Library
+from django.urls import reverse
+
+from ..forms import AttachmentForm
+from ..models import Attachment
+from ..views import add_url_for_obj
+
+register = Library()
+
+
+@register.inclusion_tag("attachments/add_form.html", takes_context=True)
+def attachment_form(context, obj, **kwargs):
+ """
+ Renders a "upload attachment" form.
+
+ The user must own ``attachments.add_attachment permission`` to add
+ attachments.
+ """
+ if context["user"].has_perm("attachments.add_attachment"):
+ return {
+ "form": AttachmentForm(),
+ "form_url": add_url_for_obj(obj),
+ "next": kwargs.get("next", context.request.build_absolute_uri()),
+ }
+ else:
+ return {"form": None}
+
+
+@register.inclusion_tag("attachments/delete_link.html", takes_context=True)
+def attachment_delete_link(context, attachment, **kwargs):
+ """
+ Renders a html link to the delete view of the given attachment. Returns
+ no content if the request-user has no permission to delete attachments.
+
+ The user must own either the ``attachments.delete_attachment`` permission
+ and is the creator of the attachment, that he can delete it or he has
+ ``attachments.delete_foreign_attachments`` which allows him to delete all
+ attachments.
+ """
+ if context["user"].has_perm("attachments.delete_foreign_attachments") or (
+ context["user"] == attachment.creator
+ and context["user"].has_perm("attachments.delete_attachment")
+ ):
+ return {
+ "next": kwargs.get("next", context.request.build_absolute_uri()),
+ "delete_url": reverse(
+ "attachments:delete", kwargs={"attachment_pk": attachment.pk}
+ ),
+ }
+ return {"delete_url": None}
+
+
+@register.simple_tag
+def attachments_count(obj):
+ """
+ Counts attachments that are attached to a given object::
+
+ {% attachments_count obj %}
+ """
+ attachments_count = Attachment.objects.attachments_for_object(obj).count()
+ print("obj: {}, attachments_count: {}".format(obj.id, attachments_count))
+
+ return attachments_count
+
+
+@register.simple_tag
+def get_attachments_for(obj, *args, **kwargs):
+ """
+ Resolves attachments that are attached to a given object. You can specify
+ the variable name in the context the attachments are stored using the `as`
+ argument.
+
+ Syntax::
+
+ {% get_attachments_for obj as "my_attachments" %}
+ """
+ return Attachment.objects.attachments_for_object(obj)
diff --git a/django/attachments/tests/__init__.py b/django/attachments/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/django/attachments/tests/base.py b/django/attachments/tests/base.py
new file mode 100644
index 0000000..32d6945
--- /dev/null
+++ b/django/attachments/tests/base.py
@@ -0,0 +1,68 @@
+# -*- encoding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.contrib.auth.models import Permission, User
+from django.contrib.contenttypes.models import ContentType
+from django.core.files.uploadedfile import SimpleUploadedFile
+from django.test import TestCase
+from django.urls import reverse
+
+from ..models import Attachment
+from .testapp.models import TestModel
+
+
+class BaseTestCase(TestCase):
+ target_model_class = TestModel
+
+ def setUp(self):
+ """
+ Create two users with `attachments.add_attachment` permission
+ and one object to attach files to.
+ """
+ content_type = ContentType.objects.get_for_model(Attachment)
+ self.add_permission = Permission.objects.get(
+ content_type=content_type, codename="add_attachment"
+ )
+ self.del_permission = Permission.objects.get(
+ content_type=content_type, codename="delete_attachment"
+ )
+
+ self.del_foreign_permission = Permission.objects.get(
+ content_type=content_type, codename="delete_foreign_attachments"
+ )
+
+ self.cred_jon = {"username": "jon", "password": "foobar"}
+ self.cred_jane = {"username": "jane", "password": "foobar"}
+ self.jon = User.objects.create_user(**self.cred_jon)
+ self.jon.user_permissions.add(self.add_permission)
+ self.jon.user_permissions.add(self.del_permission)
+
+ self.jane = User.objects.create_user(**self.cred_jane)
+ self.jane.user_permissions.add(self.add_permission)
+ self.jane.user_permissions.add(self.del_permission)
+
+ self.obj = self.target_model_class.objects.create(title="My first test item")
+
+ def _upload_testfile(self, file_obj=None, file_content=b"file content", **extra):
+ """
+ Uploads a sample file for the given user.
+ """
+ add_url = reverse(
+ "attachments:add",
+ kwargs={
+ "app_label": "testapp",
+ "model_name": self.target_model_class.__name__.lower(),
+ "pk": self.obj.pk,
+ },
+ )
+
+ if not file_obj:
+ file_obj = SimpleUploadedFile(
+ "Ünicode Filename 🙂.jpg",
+ file_content,
+ content_type="image/jpeg",
+ )
+ return self.client.post(
+ add_url, {"attachment_file": file_obj}, follow=True,
+ **extra
+ )
diff --git a/django/attachments/tests/test_integrity.py b/django/attachments/tests/test_integrity.py
new file mode 100644
index 0000000..68ac80c
--- /dev/null
+++ b/django/attachments/tests/test_integrity.py
@@ -0,0 +1,19 @@
+from django.core.management import call_command
+from django.test import TestCase
+from six import StringIO
+
+
+class IntegrityTestCase(TestCase):
+ """
+ Very basic tests around the app itself, not the code.
+ """
+
+ def test_no_pending_migrations(self):
+ """
+ Make sure all model changes are reflected with Django migrations.
+ """
+ output = StringIO()
+ call_command(
+ "makemigrations", "--dry-run", interactive=False, stdout=output
+ )
+ self.assertTrue("No changes detected" in output.getvalue())
diff --git a/django/attachments/tests/test_template.py b/django/attachments/tests/test_template.py
new file mode 100644
index 0000000..1cac854
--- /dev/null
+++ b/django/attachments/tests/test_template.py
@@ -0,0 +1,69 @@
+from django.urls import reverse
+
+from ..models import Attachment
+from .base import BaseTestCase
+
+
+class ViewTestCase(BaseTestCase):
+ def setUp(self):
+ super(ViewTestCase, self).setUp()
+ self.item_url = reverse("testapp-detail", kwargs={"pk": self.obj.pk})
+
+ def test_uploaded_attachment_urls_are_listed(self):
+ self.client.login(**self.cred_jon)
+ self._upload_testfile()
+ response = self.client.get(self.item_url)
+ attachment = Attachment.objects.attachments_for_object(self.obj)[0]
+ self.assertTrue(attachment.attachment_file.url in str(response.content))
+
+ def test_attachment_count_is_listed(self):
+ self.client.login(**self.cred_jon)
+ self._upload_testfile()
+ self._upload_testfile()
+ response = self.client.get(self.item_url)
+ attachment_count = Attachment.objects.attachments_for_object(
+ self.obj
+ ).count()
+ self.assertTrue(
+ "Object has %d attachments" % attachment_count
+ in str(response.content)
+ )
+
+ def test_upload_form_is_listed_with_add_permission(self):
+ self.client.login(**self.cred_jon)
+ response = self.client.get(self.item_url)
+ self.assertTrue("
+
+
+
+
Community-trusted Electrum servers
+
+
+ Mainnet:
+
+ electrum.emzy.de / s50002
+ electrum.blockstream.info / s50002
+
+
+
+
+
+ Testnet:
+
+ testnet.qtornado.com / s51002
+ testnet.aranguren.org / s51002
+
+
+
+
+
+
+
+
+ {% csrf_token %}
+
+
+ {{ form|bootstrap }}
+
+
+
+
+
+
+ Update Electrum Server
+
+
+
+
+
+
+
+
+
+
+
+
+
{% endblock %}
diff --git a/django/templates/label_edit.html b/django/templates/label_edit.html
index aa2f9fd..ae6f434 100644
--- a/django/templates/label_edit.html
+++ b/django/templates/label_edit.html
@@ -2,14 +2,38 @@
{% load i18n %}
{% load sekizai_tags %}
{% load labelbase_tags %}
+{% load backgroundtask_tags %}
+{% load attachments_tags %}
+
+{% block title %}{{ object.type }} {{ object.ref }}{% endblock %}
+
{% block content %}
+{% get_attachments_for object.get_label_attachment as my_attachments %}
Label Detail
+ {% is_self_hosted as my_labelbase_is_self_hosted %}
+ {% if object.labelbase.user.profile.use_attachments %}
+
+ {% if my_labelbase_is_self_hosted %}
+
+ Attachments
+ {% if my_attachments.count %}
+ {% attachments_count object.get_label_attachment %}
+ {% endif %}
+
+ {% else %}
+
+ Attachments
+
+ {% endif %}
+
+ {% endif %}
{% comment %}
+ Output in queue! This output is currently in the processing queue. It will be checked shortly.
+
+ {% else %}
+
+
+ {% if object.type == "output" %}
+ {% switch output.get_spent_status %}
+ {% case "spent" %}
+
+ Output spent! Blockchain records indicate that this output has been spent in another transaction.
+
+ {% case "unspent" %}
+
+ Output unspent! Blockchain records indicate that this output has not been spent yet.
+
+ {% case "unconfirmed" %}
+
+
+
+ Actions
+
+
+
+
Output unconfirmed! Blockchain records indicate that this output has not been confirmed yet.
+
+ {% endswitch %}
+ {% endif %}
+
+
+
{% endif %}
+
+
@@ -128,6 +186,7 @@
{% endif %}
{% endcomment %}
+
{% block label_edit_content %}
{% endblock %}
diff --git a/django/templates/label_edit_attachments.html b/django/templates/label_edit_attachments.html
new file mode 100644
index 0000000..66fed5d
--- /dev/null
+++ b/django/templates/label_edit_attachments.html
@@ -0,0 +1,45 @@
+{% extends "label_edit.html" %}
+{% load bootstrap %}
+{% load i18n %}
+{% load attachments_tags %}
+
+
+{% block label_edit_content %}
+
+
+
+
+{% if my_attachments %}
+
+
+
+ #
+ Attachment
+ Action
+
+
+
+ {% for attachment in my_attachments %}
+
+ {{ attachment.id }}
+ {{ attachment.filename }}
+ {% attachment_delete_link attachment %}
+
+ {% empty %}
+
+ {% endfor %}
+
+
+
+{% else %}
+
+
+Label has {% attachments_count object.get_label_attachment %} attachments.
+
+
+{% endif %}
+
+
+{% attachment_form form.instance %}
+
+{% endblock %}
diff --git a/django/templates/label_edit_output_details.html b/django/templates/label_edit_output_details.html
index d3738ef..36e0273 100644
--- a/django/templates/label_edit_output_details.html
+++ b/django/templates/label_edit_output_details.html
@@ -7,7 +7,7 @@
-{% if action == "output-details" %}
+{% if action == "label-attachments" %}
{% if form.instance.type == "output" %}
@@ -16,8 +16,21 @@
Confirmed at block height: {{ output.confirmed_at_block_height }}
Confirmed at block time: {{ output.confirmed_at_block_time }}
Network: {{ output.get_network_display }}
+ {% if output.next_input_attributes %}
+ Fee estimation will be made based on:
+
+ {{ output.next_input_attributes }}
+
+ {% if output.next_input_attributes.input_n %}
+ * Assuming {{ output.next_input_attributes.input_m }}-of-{{ output.next_input_attributes.input_n }} multisig
+ {% else %}
+ {% endif %}
+ {% endif %}
+
{% endif %}
+
+
{% endif %}
{% endblock %}
diff --git a/django/templates/label_edit_update.html b/django/templates/label_edit_update.html
index a192799..70b535f 100644
--- a/django/templates/label_edit_update.html
+++ b/django/templates/label_edit_update.html
@@ -1,7 +1,7 @@
{% extends "label_edit.html" %}
{% load bootstrap %}
{% load i18n %}
-
+{% load attachments_tags %}
{% block label_edit_content %}
+
+
{% endblock %}
diff --git a/django/templates/profile.html b/django/templates/profile.html
index 3814e85..f8fb3dd 100644
--- a/django/templates/profile.html
+++ b/django/templates/profile.html
@@ -1,8 +1,13 @@
{% extends "_base.html" %}
{% load i18n %}
{% load sekizai_tags %}
+{% load labelbase_tags %}
+
+
+
+
{% block content %}
-
+
diff --git a/django/userprofile/forms.py b/django/userprofile/forms.py
index 65897ac..6868a1b 100644
--- a/django/userprofile/forms.py
+++ b/django/userprofile/forms.py
@@ -23,13 +23,18 @@ class MempoolForm(forms.ModelForm):
class ElectrumServerInfoForm(forms.ModelForm):
class Meta:
model = Profile
- fields = ['electrum_hostname', 'electrum_ports']
+ fields = ['electrum_hostname', 'electrum_ports',
+ 'electrum_hostname_test', 'electrum_ports_test']
labels = {
- 'electrum_hostname': 'Electrum Hostname',
- 'electrum_ports': 'Electrum Protocol + Port',
+ 'electrum_hostname': 'Electrum Hostname (Mainnet)',
+ 'electrum_ports': 'Electrum Protocol + Port (Mainnet)',
+ 'electrum_hostname_test': 'Electrum Hostname (Testnet)',
+ 'electrum_ports_test': 'Electrum Protocol + Port (Testnet)',
}
+
+
class ProfileCurrencyForm(forms.ModelForm):
class Meta:
model = Profile
diff --git a/django/userprofile/migrations/0016_auto_20240329_2233.py b/django/userprofile/migrations/0016_auto_20240329_2233.py
new file mode 100644
index 0000000..17b45f9
--- /dev/null
+++ b/django/userprofile/migrations/0016_auto_20240329_2233.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.24 on 2024-03-29 22:33
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('userprofile', '0015_alter_profile_use_sentry'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='profile',
+ name='electrum_hostname_test',
+ field=models.CharField(blank=True, default='testnet.qtornado.com', max_length=255),
+ ),
+ migrations.AddField(
+ model_name='profile',
+ name='electrum_ports_test',
+ field=models.CharField(blank=True, default='t51001', max_length=6),
+ ),
+ ]
diff --git a/django/userprofile/migrations/0017_alter_profile_electrum_ports_test.py b/django/userprofile/migrations/0017_alter_profile_electrum_ports_test.py
new file mode 100644
index 0000000..3ef7a75
--- /dev/null
+++ b/django/userprofile/migrations/0017_alter_profile_electrum_ports_test.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.25 on 2024-03-29 23:39
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('userprofile', '0016_auto_20240329_2233'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='profile',
+ name='electrum_ports_test',
+ field=models.CharField(blank=True, default='s51002', max_length=6),
+ ),
+ ]
diff --git a/django/userprofile/migrations/0018_profile_use_attachments.py b/django/userprofile/migrations/0018_profile_use_attachments.py
new file mode 100644
index 0000000..44b4893
--- /dev/null
+++ b/django/userprofile/migrations/0018_profile_use_attachments.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.25 on 2024-04-13 22:55
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('userprofile', '0017_alter_profile_electrum_ports_test'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='profile',
+ name='use_attachments',
+ field=models.BooleanField(default=True),
+ ),
+ ]
diff --git a/django/userprofile/models.py b/django/userprofile/models.py
index ec3d478..ebfbd77 100644
--- a/django/userprofile/models.py
+++ b/django/userprofile/models.py
@@ -26,6 +26,10 @@ class Profile(models.Model):
electrum_hostname = models.CharField(max_length=255, default="electrum.emzy.de", blank=True)
electrum_ports = models.CharField(max_length=6, default="s50002", blank=True)
+ # electrum testnet
+ electrum_hostname_test = models.CharField(max_length=255, default="testnet.qtornado.com", blank=True)
+ electrum_ports_test = models.CharField(max_length=6, default="s51002", blank=True)
+
avatar_url = models.CharField(max_length=160,
default="/static/avatar.jpg",
blank=True,
@@ -35,6 +39,7 @@ class Profile(models.Model):
auto_cleanup = models.BooleanField(default=False)
use_hashtags = models.BooleanField(default=True)
use_treemap = models.BooleanField(default=True)
+ use_attachments = models.BooleanField(default=True)
use_sentry = models.BooleanField(default=True)
use_chatwoot = models.BooleanField(default=False)
use_fiatfinances = models.BooleanField(default=True)
diff --git a/django/userprofile/views.py b/django/userprofile/views.py
index e2342c2..ae72416 100644
--- a/django/userprofile/views.py
+++ b/django/userprofile/views.py
@@ -92,6 +92,10 @@ class ElectrumInfoUpdateView(UpdateView):
profile.electrum_hostname = profile._meta.get_field('electrum_hostname').get_default()
if not profile.electrum_ports:
profile.electrum_ports = profile._meta.get_field('electrum_ports').get_default()
+ if not profile.electrum_hostname_test:
+ profile.electrum_hostname_test = profile._meta.get_field('electrum_hostname_test').get_default()
+ if not profile.electrum_ports_test:
+ profile.electrum_ports_test = profile._meta.get_field('electrum_ports_test').get_default()
profile.save()
messages.success(self.request, "
Success! Electrum ServerInfo updated successfully.")
return super().form_valid(form)
diff --git a/nginx/nginx.conf b/nginx/nginx.conf
index 01a1f59..5e48f34 100644
--- a/nginx/nginx.conf
+++ b/nginx/nginx.conf
@@ -14,7 +14,7 @@ http {
upstream labelbase_django {
server labelbase_django:8000;
}
-
+
server {
listen 8080;
client_max_body_size 69M;
@@ -46,6 +46,14 @@ http {
break;
}
+ location /attachments/attachment {
+ autoindex off;
+ index index.html;
+ root /app;
+ break;
+ }
+
+
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;