def test_basic_iter(self):
        class TestForm(forms.Form):
            first_name = forms.CharField(label='First name', required=False)
            last_name = forms.CharField(label='Last name')
            phone = forms.CharField(label='Phone')
            cell = forms.CharField(label='Cell')
            fax = forms.CharField(label='Fax')

        fbm = FieldBlockManager(
            ('names', 'Names', ('first_name', 'last_name')),
            ('details', 'Details', ('cell', 'phone', 'fax')),
        )
        form = TestForm()

        with self.assertNoException():
            blocks_list = [*fbm.build(form)]

        self.assertEqual(2, len(blocks_list))

        names_group = blocks_list[0]
        self.assertIsInstance(names_group, tuple)
        self.assertEqual(2, len(names_group))
        self.assertEqual('Names', names_group[0])

        details_group = blocks_list[1]
        self.assertEqual('Details', details_group[0])
    def test_new01(self):
        class TestForm(forms.Form):
            first_name = forms.CharField(label='First name')
            last_name = forms.CharField(label='Last name')
            phone = forms.CharField(label='Phone')
            cell = forms.CharField(label='Cell')
            fax = forms.CharField(label='Fax')

        fbm1 = FieldBlockManager(
            ('names', 'Names', ('last_name', 'first_name')), )
        fbm2 = fbm1.new(('details', 'Details', ('cell', 'phone', 'fax')), )
        self.assertIsInstance(fbm2, FieldBlockManager)
        self.assertIsNot(fbm2, fbm1)

        form = TestForm()

        blocks = fbm2.build(form)
        with self.assertNoException():
            names_group = blocks['names']

        self.assertEqual('Names', names_group[0])
        self.assertListEqual(
            ['last_name', 'first_name'],
            [bfield.name for bfield, required in names_group[1]])

        with self.assertNoException():
            details_group = blocks['details']

        self.assertEqual('Details', details_group[0])
        self.assertListEqual(
            ['cell', 'phone', 'fax'],
            [bfield.name for bfield, required in details_group[1]])
    def test_basic_get_item(self):
        class TestForm(forms.Form):
            first_name = forms.CharField(label='First name', required=False)
            last_name = forms.CharField(label='Last name')
            phone = forms.CharField(label='Phone')
            cell = forms.CharField(label='Cell')
            fax = forms.CharField(label='Fax')

        fbm = FieldBlockManager(
            ('names', 'Names', ('first_name', 'last_name')),
            ('details', 'Details', ['cell', 'phone', 'fax']),
        )
        form = TestForm()

        blocks = fbm.build(form)
        with self.assertNoException():
            names_group = blocks['names']

        self.assertIsInstance(names_group, tuple)
        self.assertEqual(2, len(names_group))
        self.assertEqual('Names', names_group[0])

        items = names_group[1]
        self.assertIsInstance(items, list)
        self.assertEqual(2, len(items))

        # --
        item1 = items[0]
        self.assertIsInstance(item1, tuple)
        self.assertEqual(2, len(item1))
        self.assertIs(item1[1], False)

        bound_field1 = item1[0]
        self.assertIsInstance(bound_field1, BoundField)
        self.assertEqual('first_name', bound_field1.name)
        self.assertEqual('id_first_name', bound_field1.auto_id)

        # --
        bfield2, required2 = items[1]
        self.assertEqual('last_name', bfield2.name)
        self.assertIs(required2, True)

        # --
        with self.assertNoException():
            details_group = blocks['details']

        self.assertEqual('Details', details_group[0])
        self.assertListEqual(
            ['cell', 'phone', 'fax'],  # The order of the block info is used
            [bfield.name for bfield, required in details_group[1]])

        # ---
        with self.assertRaises(KeyError):
            __ = blocks['names']  # Already pop
    def test_new03(self):
        "Extend parent wildcard => error."
        fbm1 = FieldBlockManager(('names', 'Names', '*'), )

        with self.assertRaises(ValueError) as cm:
            __ = fbm1.new(
                ('names', 'Names', ['cell']),
                ('details', 'Details', ('phone', 'fax')),
            )

        self.assertEqual(
            'You cannot extend a wildcard (see the form-block with category "names")',
            str(cm.exception))
    def test_invalid_field01(self):
        class TestForm(forms.Form):
            last_name = forms.CharField(label='Last name')

        fbm = FieldBlockManager(('names', 'Names', ('invalid', 'last_name')), )
        form = TestForm()

        with self.assertNoException():
            blocks = fbm.build(form)

        with self.assertNoException():
            group = blocks['names']

        self.assertListEqual(['last_name'],
                             [bfield.name for bfield, required in group[1]])
Exemple #6
0
class CustomFormGroupForm(CremeModelForm):
    name = forms.CharField(label=_('Name'), max_length=100)
    cells = CustomFormCellsField(label=_('Fields'))

    blocks = FieldBlockManager(
        {
            'id': 'name',
            'label': 'Name',
            'fields': ('name',),
        }, {
            'id': 'cells',
            'label': 'Fields',
            'fields': ('cells',),
        },
    )

    class Meta:
        model = CustomFormConfigItem
        fields = ()

    def __init__(self, descriptor, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.descriptor = descriptor
        registry = descriptor.build_cell_registry()

        class FinalCellExtraFieldsField(CFormCellExtraFieldsField):
            cell_class = registry[EntityCellCustomFormExtra.type_id]

        cells_f = self.fields['cells']
        cells_f.field_classes = {
            *cells_f.field_classes,
            FinalCellExtraFieldsField,
        }
        cells_f.cell_registry = registry
Exemple #7
0
class CampaignAddListForm(CremeForm):
    messaging_lists = MultiCreatorEntityField(label=_('Lists'), required=False, model=MessagingList)

    # error_messages = {
    #     'already_linked': _('Following lists are already related to this campaign: %(lists)s'),
    # }

    blocks = FieldBlockManager(('general', _('Messaging lists'), '*'))

    def __init__(self, entity, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.campaign = entity
        self.fields['messaging_lists'].q_filter = ~Q(
            id__in=[*entity.lists.values_list('id', flat=True)],
        )

    # # In fact duplicate is not a problem with django's m2m
    # def clean_lists(self):
    #     messaging_lists = self.cleaned_data['messaging_lists']
    #     current_lists   = frozenset(self.campaign.lists.values_list('pk', flat=True))
    #     duplicate       = [mlist for mlist in messaging_lists if mlist.id in current_lists]
    #
    #     if duplicate:
    #         raise ValidationError(
    #             self.error_messages['already_linked'],
    #             params={'lists': ', '.join(mlist.name for mlist in duplicate)},
    #             code='already_linked',
    #         )
    #
    #     return messaging_lists

    def save(self):
        add_mlist = self.campaign.lists.add
        for mlist in self.cleaned_data['messaging_lists']:
            add_mlist(mlist)
Exemple #8
0
class MailingListAddRecipientsForm(CremeForm):
    recipients = MultiEmailField(
        label=_('Recipients'),
        help_text=_('Write a valid e-mail address per line.'),
    )

    blocks = FieldBlockManager({
        'id': 'general',
        'label': _('Recipients'),
        'fields': '*',
    })

    def __init__(self, entity, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.ml = entity

    def save(self):
        ml = self.ml
        recipients = self.cleaned_data['recipients']
        existing = frozenset(
            EmailRecipient.objects.filter(
                ml=ml, address__in=recipients).values_list('address',
                                                           flat=True))

        create = EmailRecipient.objects.create

        for address in recipients:
            if address not in existing:
                create(ml=ml, address=address)
Exemple #9
0
class AddContactsFromFilterForm(_AddPersonsFromFilterForm):
    blocks = FieldBlockManager(('general', _('Contacts recipients'), '*'))

    person_model = Contact

    def get_persons_m2m(self):
        return self.ml.contacts
Exemple #10
0
class AddOrganisationsFromFilterForm(_AddPersonsFromFilterForm):
    blocks = FieldBlockManager(('general', _('Organisations recipients'), '*'))

    person_model = Organisation

    def get_persons_m2m(self):
        return self.ml.organisations
Exemple #11
0
class AddChildForm(CremeForm):
    child = CreatorEntityField(label=_('List'), model=MailingList)

    error_messages = {
        'own_child': _("A list can't be its own child"),
        'in_parents': _('List already in the parents'),
        'in_children': _('List already in the children'),
    }

    blocks = FieldBlockManager(('general', _('Child mailing list'), '*'))

    def __init__(self, entity, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.ml = entity

    def clean_child(self):
        child = self.cleaned_data['child']
        ml = self.ml

        if ml.id == child.id:
            raise ValidationError(self.error_messages['own_child'],
                                  code='own_child')

        if ml.already_in_parents(child.id):
            raise ValidationError(self.error_messages['in_parents'],
                                  code='in_parents')

        if ml.already_in_children(child.id):
            raise ValidationError(self.error_messages['in_children'],
                                  code='in_children')

        return child

    def save(self):
        self.ml.children.add(self.cleaned_data['child'])
Exemple #12
0
class UserRoleCredentialsGeneralStep(CredentialsGeneralStep):
    blocks = FieldBlockManager(
        ('general', _('First credentials: main information'), '*'),
        (
            'actions',
            _('First credentials: actions'),
            ['can_view', 'can_change', 'can_delete', 'can_link', 'can_unlink'],
        ),
    )

    def __init__(self, role, *args, **kwargs):
        self.role = role
        super().__init__(*args, **kwargs)
        fields = self.fields
        fields['can_view'] = forms.CharField(
            label=fields['can_view'].label,
            required=False,
            widget=creme_widgets.Label,
            initial=_('Yes'),
        )

    def clean_can_view(self):
        return True

    def _get_allowed_apps(self):
        return self.role.allowed_apps

    def save(self, commit=False, *args, **kwargs):
        self.instance.role = self.role
        return super().save(commit=commit, *args, **kwargs)
Exemple #13
0
class MessagingListAddRecipientsForm(CremeForm):
    # TODO: see for phonelist widget
    recipients = PhoneListField(
        widget=Textarea, label=_('Recipients'),
        help_text=_('One phone number per line'),
    )

    blocks = FieldBlockManager({
        'id': 'general', 'label': _('Recipients'), 'fields': '*',
    })

    def __init__(self, entity, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.messaging_list = entity

    def save(self):
        messaging_list = self.messaging_list
        recipients = self.cleaned_data['recipients']
        existing = frozenset(
            Recipient.objects.filter(
                messaging_list=messaging_list,
                phone__in=recipients,
            ).values_list('phone', flat=True)
        )

        create = Recipient.objects.create

        for number in recipients:
            if number not in existing:
                create(messaging_list=messaging_list, phone=number)
class RelationTypeCreateForm(CremeForm):
    subject_ctypes     = _CTypesField()
    subject_properties = _PropertyTypesField(help_text=_('The subject must have all the selected properties.'))

    subject_predicate   = CharField(label=_('Subject => object'))
    subject_is_copiable = BooleanField(label=_('Direct relationship is copiable'), initial=True, required=False,
                                       help_text=_('Are the relationships with this type copied '
                                                   'when the subject entity is cloned?'
                                                  ),
                                      )
    subject_min_display = BooleanField(label=_("Display once on the subject's page"), required=False,
                                       help_text=_('Do not display in the «Relationships» block (detail-view of '
                                                   'subject) when it is already displayed by another block.'
                                                  ),
                                      )

    object_predicate   = CharField(label=_('Object => subject'))
    object_is_copiable = BooleanField(label=_('Symmetrical relationship is copiable'), initial=True, required=False,
                                      help_text=_('Are the relationships with this type copied '
                                                  'when the object entity is cloned?'
                                                 ),
                                     )
    object_min_display = BooleanField(label=_("Display once on the subject's page"), required=False,
                                      help_text=_('Do not display in the «Relationships» block (detail-view of '
                                                  'object) when it is already displayed by another block.'
                                                 ),
                                     )

    object_ctypes      = _CTypesField()
    object_properties  = _PropertyTypesField(help_text=_('The object must have all the selected properties.'))

    blocks = FieldBlockManager(('subject',   _('Subject'),        ('subject_ctypes', 'subject_properties')),
                               ('predicate', _('Verb/Predicate'), ('subject_predicate', 'subject_is_copiable', 'subject_min_display',
                                                                   'object_predicate',  'object_is_copiable',  'object_min_display',
                                                                  )
                               ),
                               ('object',    _('Object'),         ('object_ctypes', 'object_properties')),
                              )

    def __init__(self, instance=None, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def save(self, pk_subject='creme_config-subject_userrelationtype',
             pk_object='creme_config-object_userrelationtype',
             generate_pk=True, *args, **kwargs
            ):
        get_data = self.cleaned_data.get

        subject_ctypes = [ct.model_class() for ct in get_data('subject_ctypes')]
        object_ctypes  = [ct.model_class() for ct in get_data('object_ctypes')]

        return RelationType.create((pk_subject, get_data('subject_predicate'), subject_ctypes, get_data('subject_properties')),
                                   (pk_object,  get_data('object_predicate'),  object_ctypes,  get_data('object_properties')),
                                   is_custom=True, generate_pk=generate_pk,
                                   is_copiable=(get_data('subject_is_copiable'), get_data('object_is_copiable')),
                                   minimal_display=(get_data('subject_min_display'), get_data('object_min_display')),
                                  )
Exemple #15
0
class AddContactsFromFilterForm(AddPersonsFromFilterForm):
    blocks = FieldBlockManager({
        'id': 'general', 'label': _('Contacts recipients'), 'fields': '*',
    })

    person_model = Contact

    def get_persons_m2m(self):
        return self.messaging_list.contacts
Exemple #16
0
class UserRoleCredentialsFilterStep(CredentialsFilterStep):
    blocks = FieldBlockManager(
        ('general', _('First credentials: filter'), ('name', 'use_or')),
        ('conditions', _('First credentials: conditions'), '*'),
    )

    def __init__(self, role, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.role = role  # NB: not currently used, but facilitate extending
    def test_wildcard03(self):
        "Several wildcards => error."

        class TestForm(forms.Form):
            first_name = forms.CharField(label='First name')
            last_name = forms.CharField(label='Last name')
            phone = forms.CharField(label='Phone')
            cell = forms.CharField(label='Cell')

        fbm = FieldBlockManager(
            ('names', 'Names', '*'),
            ('details', 'Details', '*'),
        )

        with self.assertRaises(ValueError) as cm:
            __ = fbm.build(TestForm())

        self.assertEqual('Only one wildcard is allowed: {}'.format(TestForm),
                         str(cm.exception))
Exemple #18
0
class AddOrganisationsFromFilterForm(_AddPersonsFromFilterForm):
    blocks = FieldBlockManager({
        'id': 'general',
        'label': _('Organisations recipients'),
        'fields': '*',
    })

    person_model = Organisation

    def get_persons_m2m(self):
        return self.ml.organisations
Exemple #19
0
class MailingListAddCSVForm(CremeForm):
    recipients = FileField(label=_('Recipients'),
                           help_text=_(
                               'A file containing one e-mail address per line '
                               '(eg:[email protected] without quotation marks).'))

    blocks = FieldBlockManager({
        'id': 'general',
        'label': _('CSV file'),
        'fields': '*',
    })

    def __init__(self, entity, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.ml = entity

    @staticmethod
    def filter_mail_chunk(value):
        # result = smart_text(value.strip())
        result = smart_str(value.strip())

        try:
            validate_email(result)
        except ValidationError:
            result = None

        return result

    def save(self
             ):  # TODO: factorise with MailingListAddRecipientsForm.save() ??
        ml = self.ml
        create = EmailRecipient.objects.create
        filter_ = EmailRecipient.objects.filter

        uploaded_file = self.cleaned_data['recipients']

        # TODO: genexpr
        def addresses():
            for line in uploaded_file:
                address = self.filter_mail_chunk(line)
                if address:
                    yield address

        for recipients in chunktools.iter_as_chunk(addresses(), 256):
            recipients = frozenset(recipients)
            existing = frozenset(
                filter_(ml=ml, address__in=recipients).values_list('address',
                                                                   flat=True))

            for address in recipients:
                if address not in existing:
                    create(ml=ml, address=address)
    def test_wildcard02(self):
        "Wildcard in second group."

        class TestForm(forms.Form):
            first_name = forms.CharField(label='First name', required=False)
            last_name = forms.CharField(label='Last name')
            phone = forms.CharField(label='Phone')
            cell = forms.CharField(label='Cell')
            fax = forms.CharField(label='Fax')

        fbm = FieldBlockManager(
            ('names', 'Names', '*'),
            ('details', 'Details', ('phone', 'fax', 'cell')),
        )

        blocks = fbm.build(TestForm())
        self.assertListEqual(
            ['first_name', 'last_name'],
            [bfield.name for bfield, required in blocks['names'][1]])
        self.assertListEqual(
            ['phone', 'fax', 'cell'],
            [bfield.name for bfield, required in blocks['details'][1]])
Exemple #21
0
class _SegmentForm(_AuxForm):
    name = CharField(label=_('Name'), max_length=100)

    error_messages = {
        'duplicated_name':
        _('A segment with this name already exists'),
        'duplicated_property':
        _('A property with the name «%(name)s» already exists'),
    }

    blocks = FieldBlockManager({
        'id':
        'general',
        'label':
        _('General information'),
        'fields': ['name', 'product', 'place', 'price', 'promotion'],
    })

    class Meta:
        model = MarketSegmentDescription
        exclude = (*_AuxForm.Meta.exclude, 'segment')

    # TODO: factorise with market_segment.MarketSegmentForm
    def clean_name(self):
        name = self.cleaned_data['name']
        ptype_text = MarketSegment.generate_property_text(name)

        instance = self.instance
        segments = MarketSegment.objects.filter(name=name)
        ptypes = CremePropertyType.objects.filter(text=ptype_text)

        if instance.pk:
            segment = instance.segment
            segments = segments.exclude(pk=segment.pk)
            ptypes = ptypes.exclude(pk=segment.property_type_id)

        if segments.exists():
            raise ValidationError(
                self.error_messages['duplicated_name'],
                code='duplicated_name',
            )

        if ptypes.exists():
            raise ValidationError(
                self.error_messages['duplicated_property'],
                params={'name': ptype_text},
                code='duplicated_property',
            )

        return name
Exemple #22
0
class EmailTemplateAddAttachment(CremeForm):
    attachments = MultiCreatorEntityField(label=_('Attachments'), required=False, model=Document)

    blocks = FieldBlockManager(('general', _('Attachments'), '*'))

    def __init__(self, entity, *args, **kwargs):
        # super(EmailTemplateAddAttachment, self).__init__(*args, **kwargs)
        super().__init__(*args, **kwargs)
        self.template = entity

    def save(self):
        attachments = self.template.attachments

        for attachment in self.cleaned_data['attachments']:
            attachments.add(attachment)
Exemple #23
0
class CampaignAddMLForm(CremeForm):
    mailing_lists = MultiCreatorEntityField(label=_('Lists'),
                                            required=False,
                                            model=get_mailinglist_model())

    blocks = FieldBlockManager(('general', _('Mailing lists'), '*'))

    def __init__(self, entity, *args, **kwargs):
        # super(CampaignAddMLForm, self).__init__(*args, **kwargs)
        super().__init__(*args, **kwargs)
        self.campaign = entity
        # self.fields['mailing_lists'].q_filter = {'~id__in': list(entity.mailing_lists.values_list('id', flat=True))}
        self.fields['mailing_lists'].q_filter = \
            ~Q(id__in=list(entity.mailing_lists.values_list('id', flat=True)))

    def save(self):
        add_ml = self.campaign.mailing_lists.add
        for ml in self.cleaned_data['mailing_lists']:
            add_ml(ml)
Exemple #24
0
class AddOrganisationsForm(CremeForm):  # TODO: factorise
    recipients = MultiCreatorEntityField(
        label=_('Organisations'),
        required=False,
        model=Organisation,
    )  # other filter (name + email)??

    blocks = FieldBlockManager(('general', _('Organisations recipients'), '*'))

    def __init__(self, entity, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.ml = entity

    def save(self):
        organisations = self.ml.organisations

        # TODO: check if email if ok ????
        for organisation in self.cleaned_data['recipients']:
            organisations.add(organisation)
Exemple #25
0
class AddContactsForm(CremeForm):
    recipients = MultiCreatorEntityField(
        label=_('Contacts'),
        required=False,
        model=Contact,
    )  # other filter (name + email)??

    blocks = FieldBlockManager(('general', _('Contacts recipients'), '*'))

    def __init__(self, entity, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.ml = entity

    def save(self):
        contacts = self.ml.contacts

        # TODO: check if email if ok ????
        for contact in self.cleaned_data['recipients']:
            contacts.add(contact)
Exemple #26
0
class UserRoleCredentialsStep(AddCredentialsForm):
    blocks = FieldBlockManager(('general', _('First credentials'), '*'))

    def __init__(self, allowed_app_names, *args, **kwargs):
        self.allowed_app_names = allowed_app_names
        # super(UserRoleCredentialsStep, self).__init__(*args, **kwargs)
        super().__init__(*args, **kwargs)
        self.fields['can_view'] = CharField(
            label=_('Can view'),
            required=False,
            widget=Label,
            initial=_('Yes'),
        )

    def _get_allowed_apps(self):
        return self.allowed_app_names

    def clean_can_view(self):
        return True
Exemple #27
0
class BrickDetailviewLocationsAddForm(_BrickDetailviewLocationsForm):
    role = ModelChoiceField(
        label=_('Role'),
        queryset=UserRole.objects.none(),
        empty_label=None,
        required=False,
    )

    # TODO: manage Meta.fields in '*'
    blocks = FieldBlockManager(
        ('general', _('Configuration'), ('role', 'hat', 'top', 'left', 'right',
                                         'bottom')))

    def __init__(self, *args, **kwargs):
        # super(BrickDetailviewLocationsAddForm, self).__init__(*args, **kwargs)
        super().__init__(*args, **kwargs)
        fields = self.fields

        role_f = fields['role']
        used_role_ids = set(
            BrickDetailviewLocation.objects.filter(
                content_type=self.ct).exclude(role__isnull=True,
                                              superuser=False).values_list(
                                                  'role', flat=True))

        try:
            used_role_ids.remove(None)
        except KeyError:
            role_f.empty_label = '*{}*'.format(ugettext(
                'Superuser'))  # NB: browser can ignore <em> tag in <option>...

        role_f.queryset = UserRole.objects.exclude(pk__in=used_role_ids)

        hat_f = fields.get('hat')
        if hat_f:
            hat_f.initial = hat_f.choices[0][0]

    def save(self, *args, **kwargs):
        self.role = role = self.cleaned_data['role']
        self.superuser = (role is None)
        # super(BrickDetailviewLocationsAddForm, self).save(*args, **kwargs)
        super().save(*args, **kwargs)
Exemple #28
0
class CampaignAddListForm(CremeForm):
    messaging_lists = MultiCreatorEntityField(
        label=_('Lists'), required=False, model=MessagingList,
    )

    blocks = FieldBlockManager({
        'id': 'general', 'label': _('Messaging lists'), 'fields': '*',
    })

    def __init__(self, entity, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.campaign = entity
        self.fields['messaging_lists'].q_filter = ~Q(
            id__in=[*entity.lists.values_list('id', flat=True)],
        )

    def save(self):
        add_mlist = self.campaign.lists.add
        for mlist in self.cleaned_data['messaging_lists']:
            add_mlist(mlist)
Exemple #29
0
class AddContactsForm(CremeForm):
    # TODO: other filter (name + email) ?
    recipients = MultiCreatorEntityField(
        label=_('Contacts'), required=False, model=Contact,
    )

    blocks = FieldBlockManager({
        'id': 'general', 'label': _('Contacts recipients'), 'fields': '*',
    })

    def __init__(self, entity, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.messaging_list = entity

    def save(self):
        contacts = self.messaging_list.contacts

        # TODO: check if email if ok ????
        for contact in self.cleaned_data['recipients']:
            contacts.add(contact)
Exemple #30
0
class EmailTemplateAddAttachment(CremeForm):
    attachments = MultiCreatorEntityField(
        label=_('Attachments'),
        required=False,
        model=get_document_model(),
    )

    blocks = FieldBlockManager({
        'id': 'general',
        'label': _('Attachments'),
        'fields': '*',
    })

    def __init__(self, entity, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.template = entity

    def save(self):
        add = self.template.attachments.add

        for attachment in self.cleaned_data['attachments']:
            add(attachment)