• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

liqd / adhocracy-plus / 18908688697

29 Oct 2025 12:59PM UTC coverage: 44.622% (-44.5%) from 89.135%
18908688697

Pull #2986

github

web-flow
Merge 1dfde8ee7 into 445e1d498
Pull Request #2986: Draft: Speed up Github Ci Tests

3012 of 6750 relevant lines covered (44.62%)

0.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/apps/organisations/forms.py
1
import parler
×
2
from django import forms
×
3
from django.conf import settings
×
4
from django.core.exceptions import ValidationError
×
5
from django.utils.translation import gettext_lazy as _
×
6
from django_ckeditor_5.widgets import CKEditor5Widget
×
7

8
from adhocracy4 import transforms
×
9
from apps.cms.settings import helpers
×
10
from apps.contrib.widgets import ImageInputWidgetSimple
×
11
from apps.organisations.models import OrganisationTranslation
×
12
from apps.projects.models import Project
×
13

14
from .models import Organisation
×
15

16
IMPRINT_HELP = _("Here you can find an example of an {}imprint{}.")
×
17
TERMS_OF_USE_HELP = _("Here you can find an example of {}terms of use{}.")
×
18
DATA_PROTECTION_HELP = _(
×
19
    "Here you can find an example of a " "{}data protection policy{}."
20
)
21
NETIQUETTE_HELP = _("Here you can find an example of a {}netiquette{}.")
×
22

23
_external_plugin_resources = [
×
24
    (
25
        "collapsibleItem",
26
        "/static/ckeditor_collapsible/",
27
        "plugin.js",
28
    )
29
]
30

31

32
SOCIAL_MEDIA_CHOICES = [
×
33
    (1, _("Instagram Post 1080x1080")),
34
    (2, _("Instagram Story 1080x1920")),
35
    (3, _("Linkedin 1104x736")),
36
    (4, _("Twitter 1200x675")),
37
]
38

39
SOCIAL_MEDIA_SIZES = {
×
40
    1: {
41
        "title_max_length": 30,
42
        "title_size": 72,
43
        "title_y": 810,
44
        "description_max_length": 45,
45
        "description_size": 48,
46
        "description_y": 902,
47
        "img_min_width": 1080,
48
        "img_min_height": 760,
49
        "aplus_logo_width": 228,
50
        "aplus_logo_height": 56,
51
        "aplus_logo_y": 970,
52
        "org_logo_y": 80,
53
        "overall_height": 1080,
54
    },
55
    2: {
56
        "title_max_length": 30,
57
        "title_size": 72,
58
        "title_y": 1462,
59
        "description_max_length": 45,
60
        "description_size": 48,
61
        "description_y": 1554,
62
        "img_min_width": 1080,
63
        "img_min_height": 1278,
64
        "aplus_logo_width": 360,
65
        "aplus_logo_height": 88,
66
        "aplus_logo_y": 1654,
67
        "org_logo_y": 1054,
68
        "overall_height": 1920,
69
    },
70
    3: {
71
        "title_max_length": 30,
72
        "title_size": 48,
73
        "title_y": 524,
74
        "description_max_length": 45,
75
        "description_size": 40,
76
        "description_y": 588,
77
        "img_min_width": 1104,
78
        "img_min_height": 482,
79
        "aplus_logo_width": 196,
80
        "aplus_logo_height": 48,
81
        "aplus_logo_y": 634,
82
        "org_logo_y": 80,
83
        "overall_height": 736,
84
    },
85
    4: {
86
        "title_max_length": 30,
87
        "title_size": 56,
88
        "title_y": 480,
89
        "description_max_length": 45,
90
        "description_size": 40,
91
        "description_y": 548,
92
        "img_min_width": 1200,
93
        "img_min_height": 448,
94
        "aplus_logo_width": 196,
95
        "aplus_logo_height": 48,
96
        "aplus_logo_y": 602,
97
        "org_logo_y": 80,
98
        "overall_height": 675,
99
    },
100
}
101

102

103
class OrganisationForm(forms.ModelForm):
×
104
    translated_fields = [
×
105
        (
106
            "description",
107
            forms.CharField,
108
            {
109
                "label": OrganisationTranslation._meta.get_field(
110
                    "description"
111
                ).verbose_name,
112
                "help_text": OrganisationTranslation._meta.get_field(
113
                    "description"
114
                ).help_text,
115
                "max_length": OrganisationTranslation._meta.get_field(
116
                    "description"
117
                ).max_length,
118
                "widget": forms.Textarea({"rows": 4}),
119
            },
120
        ),
121
        (
122
            "slogan",
123
            forms.CharField,
124
            {
125
                "label": OrganisationTranslation._meta.get_field("slogan").verbose_name,
126
                "help_text": OrganisationTranslation._meta.get_field(
127
                    "slogan"
128
                ).help_text,
129
                "max_length": OrganisationTranslation._meta.get_field(
130
                    "slogan"
131
                ).max_length,
132
                "widget": forms.Textarea({"rows": 2}),
133
            },
134
        ),
135
        (
136
            "information",
137
            forms.CharField,
138
            {
139
                "label": OrganisationTranslation._meta.get_field(
140
                    "information"
141
                ).verbose_name,
142
                "help_text": OrganisationTranslation._meta.get_field(
143
                    "information"
144
                ).help_text,
145
                "max_length": OrganisationTranslation._meta.get_field(
146
                    "information"
147
                ).max_length,
148
                "widget": CKEditor5Widget(
149
                    config_name="collapsible-image-editor",
150
                ),
151
            },
152
        ),
153
    ]
154
    languages = [lang_code for lang_code, lang in settings.LANGUAGES]
×
155

156
    class Meta:
×
157
        model = Organisation
×
158
        fields = [
×
159
            "title",
160
            "logo",
161
            "image",
162
            "image_copyright",
163
            "url",
164
            "twitter_handle",
165
            "facebook_handle",
166
            "instagram_handle",
167
            "language",
168
        ]
169

170
    def __init__(self, *args, **kwargs):
×
171
        super().__init__(*args, **kwargs)
×
172
        self.fields["title"].widget.attrs.update({"required": "true"})
×
173
        for lang_code in self.languages:
×
174
            for name, field_cls, kwargs in self.translated_fields:
×
175
                self.instance.set_current_language(lang_code)
×
176
                field = field_cls(**kwargs)
×
177
                identifier = self._get_identifier(lang_code, name)
×
178
                field.required = False
×
179

180
                try:
×
181
                    translation = self.instance.get_translation(lang_code)
×
182
                    initial = getattr(translation, name)
×
183
                except parler.models.TranslationDoesNotExist:
×
184
                    initial = ""
×
185

186
                field.initial = initial
×
187

188
                self.fields[identifier] = field
×
189

190
    def _get_identifier(self, language, fieldname):
×
191
        return "{}__{}".format(language, fieldname)
×
192

193
    def translated(self):
×
194
        from itertools import groupby
×
195

196
        fields = [
×
197
            (field.html_name.split("__")[0], field)
198
            for field in self
199
            if "__" in field.html_name
200
        ]
201
        groups = groupby(fields, lambda x: x[0])
×
202
        values = [(lang, list(map(lambda x: x[1], group))) for lang, group in groups]
×
203
        return values
×
204

205
    def untranslated(self):
×
206
        return [field for field in self if "__" not in field.html_name]
×
207

208
    def prefilled_languages(self):
×
209
        languages = [
×
210
            lang
211
            for lang in self.languages
212
            if lang in self.data or self.instance.has_translation(lang)
213
        ]
214
        return languages
×
215

216
    def get_initial_active_tab(self):
×
217
        active_languages = self.prefilled_languages()
×
218
        if len(active_languages) > 0:
×
219
            return active_languages[0]
×
220
        else:
221
            return "de"
×
222

223
    def save(self, commit=True):
×
224
        instance = super().save(commit=commit)
×
225
        if commit is True:
×
226
            for lang_code in self.languages:
×
227
                if lang_code in self.data:
×
228
                    instance.set_current_language(lang_code)
×
229
                    for fieldname, _cls, _kwargs in self.translated_fields:
×
230
                        identifier = "{}__{}".format(lang_code, fieldname)
×
231
                        if fieldname == "information":
×
232
                            field_data = transforms.clean_html_field(
×
233
                                self.cleaned_data.get(identifier),
234
                                "collapsible-image-editor",
235
                            )
236
                        else:
237
                            field_data = self.cleaned_data.get(identifier)
×
238
                        setattr(instance, fieldname, field_data)
×
239
                    instance.save()
×
240
                elif instance.has_translation(lang_code):
×
241
                    instance.delete_translation(lang_code)
×
242
        return instance
×
243

244

245
class OrganisationLegalInformationForm(forms.ModelForm):
×
246
    class Meta:
×
247
        model = Organisation
×
248
        fields = ["imprint", "terms_of_use", "data_protection", "netiquette"]
×
249

250
    def __init__(self, *args, **kwargs):
×
251
        super().__init__(*args, **kwargs)
×
252
        self.fields["imprint"].help_text = helpers.add_link_to_helptext(
×
253
            self.fields["imprint"].help_text, "imprint", IMPRINT_HELP
254
        )
255
        self.fields["terms_of_use"].help_text = helpers.add_link_to_helptext(
×
256
            self.fields["terms_of_use"].help_text, "terms_of_use", TERMS_OF_USE_HELP
257
        )
258
        self.fields["data_protection"].help_text = helpers.add_link_to_helptext(
×
259
            self.fields["data_protection"].help_text,
260
            "data_protection_policy",
261
            DATA_PROTECTION_HELP,
262
        )
263
        self.fields["netiquette"].help_text = helpers.add_link_to_helptext(
×
264
            self.fields["netiquette"].help_text, "netiquette", NETIQUETTE_HELP
265
        )
266

267

268
class CommunicationProjectChoiceForm(forms.Form):
×
269
    def __init__(self, organisation=None, *args, **kwargs):
×
270
        super().__init__(*args, **kwargs)
×
271

272
        project_qs = Project.objects
×
273
        if organisation:
×
274
            project_qs = Project.objects.filter(organisation=organisation.id)
×
275

276
        self.fields["format"] = forms.ChoiceField(
×
277
            label=_("Social Media"),
278
            choices=SOCIAL_MEDIA_CHOICES,
279
            required=True,
280
            help_text=_(
281
                "Here you can create sharepics for social media that "
282
                "will help you get publicity for your project. You "
283
                "can choose between different formats."
284
            ),
285
        )
286

287
        self.fields["project"] = forms.ModelChoiceField(
×
288
            label=_("Select Project"),
289
            queryset=project_qs,
290
            required=True,
291
            empty_label=None,
292
            help_text=_(
293
                "Please select a project of your organisation and click select."
294
            ),
295
        )
296

297

298
class CommunicationContentCreationForm(forms.Form):
×
299
    sizes = None
×
300

301
    def __init__(self, project=None, format=None, *args, **kwargs):
×
302
        super().__init__(*args, **kwargs)
×
303

304
        self.sizes = SOCIAL_MEDIA_SIZES[format]
×
305

306
        self.fields["title"] = forms.CharField(
×
307
            max_length=self.sizes["title_max_length"],
308
            label=_("Title"),
309
            required=True,
310
            help_text=_(
311
                "This title will be displayed as a header. "
312
                "It should be max. {} characters long."
313
            ).format(self.sizes["title_max_length"]),
314
        )
315
        self.fields["description"] = forms.CharField(
×
316
            max_length=self.sizes["description_max_length"],
317
            label=_("Description"),
318
            required=True,
319
            help_text=_(
320
                "This description will be displayed below "
321
                "the title. It should briefly state the goal "
322
                "of the project in max. {} chars."
323
            ).format(self.sizes["description_max_length"]),
324
        )
325

326
        self.fields["image"] = forms.ImageField(
×
327
            label=_("Picture Upload"),
328
            required=True,
329
            widget=ImageInputWidgetSimple,
330
            help_text=_(
331
                "The picture will be displayed in the sharepic. It "
332
                "must be min. {} pixel wide and {} pixel tall. "
333
                "Allowed file formats are png, jpeg, gif. The file "
334
                "size should be max. 5 MB."
335
            ).format(self.sizes["img_min_width"], self.sizes["img_min_height"]),
336
        )
337

338
        self.fields["add_aplus_logo"] = forms.BooleanField(required=False, initial=True)
×
339

340
        self.fields["add_orga_logo"] = forms.BooleanField(required=False, initial=True)
×
341

342
        if project:
×
343
            self.fields["title"].initial = project.name[
×
344
                : self.sizes["title_max_length"]
345
            ]
346
            self.fields["description"].initial = project.description[
×
347
                : self.sizes["description_max_length"]
348
            ]
349

350
    def clean_image(self):
×
351
        image = self.cleaned_data["image"]
×
352
        errors = []
×
353
        image_max_mb = 5
×
354
        image_max_size = image_max_mb * 10**6
×
355

356
        if image.size > image_max_size:
×
357
            msg = _("Image should be at most {max_size} MB")
×
358
            errors.append(ValidationError(msg.format(max_size=image_max_mb)))
×
359

360
        if hasattr(image, "width"):
×
361
            image_width = image.width
×
362
        else:
363
            image_width = image.image.width
×
364
        if image_width < self.sizes["img_min_width"]:
×
365
            msg = _("Image must be at least {min_width} pixels wide")
×
366
            errors.append(
×
367
                ValidationError(msg.format(min_width=self.sizes["img_min_width"]))
368
            )
369

370
        if hasattr(image, "height"):
×
371
            image_height = image.height
×
372
        else:
373
            image_height = image.image.height
×
374
        if image_height < self.sizes["img_min_height"]:
×
375
            msg = _("Image must be at least {min_height} pixels high")
×
376
            errors.append(
×
377
                ValidationError(msg.format(min_height=self.sizes["img_min_height"]))
378
            )
379

380
        if errors:
×
381
            raise ValidationError(errors)
×
382
        return image
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc