• 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

75.31
/apps/organisations/models.py
1
from autoslug import AutoSlugField
1✔
2
from django.conf import settings
1✔
3
from django.contrib.sites.models import Site
1✔
4
from django.db import models
1✔
5
from django.urls import reverse
1✔
6
from django.utils import timezone
1✔
7
from django.utils.functional import cached_property
1✔
8
from django.utils.translation import gettext_lazy as _
1✔
9
from django_ckeditor_5.fields import CKEditor5Field
1✔
10
from jsonfield.fields import JSONField
1✔
11
from parler.models import TranslatableModel
1✔
12
from parler.models import TranslatedFields
1✔
13

14
from adhocracy4 import transforms
1✔
15
from adhocracy4.images import fields as images_fields
1✔
16
from adhocracy4.projects.models import Project
1✔
17
from apps.projects import query
1✔
18

19

20
class Organisation(TranslatableModel):
1✔
21
    slug = AutoSlugField(populate_from="name", unique=True, editable=True)
1✔
22
    name = models.CharField(max_length=512)
1✔
23
    initiators = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True)
1✔
24
    title = models.CharField(
1✔
25
        verbose_name=_("Title of your organisation"),
26
        max_length=100,
27
        help_text=_(
28
            "The title of your organisation will be shown "
29
            "on the landing page. max. 100 characters"
30
        ),
31
        blank=True,
32
    )
33
    translations = TranslatedFields(
1✔
34
        description=models.CharField(
35
            max_length=800,
36
            verbose_name=_("Short description of " "your organisation"),
37
            help_text=_(
38
                "The description will be "
39
                "displayed on the landing "
40
                "page. max. 800 characters"
41
            ),
42
            blank=True,
43
        ),
44
        slogan=models.CharField(
45
            max_length=200,
46
            verbose_name=_("Slogan"),
47
            help_text=_(
48
                "The slogan will be shown below "
49
                "the title of your organisation "
50
                "on the landing page. The slogan "
51
                "can provide context or "
52
                "additional information to the "
53
                "title. max. 200 characters"
54
            ),
55
            blank=True,
56
        ),
57
        information=CKEditor5Field(
58
            config_name="collapsible-image-editor",
59
            verbose_name=_("Information about your organisation"),
60
            help_text=_(
61
                "You can provide general information about your "
62
                "participation platform to your visitors. "
63
                "It’s also helpful to name a general person "
64
                "of contact for inquiries. The information "
65
                'will be shown on a separate "About" page that '
66
                "can be reached via the main menu."
67
            ),
68
            blank=True,
69
        ),
70
    )
71

72
    logo = images_fields.ConfiguredImageField(
1✔
73
        "logo",
74
        verbose_name=_("Logo"),
75
        help_text=_(
76
            "The Logo representing your organisation."
77
            " The image must be square and it "
78
            "should be min. 200 pixels wide and 200 "
79
            "pixels tall and max. 800 pixels wide and "
80
            "800 pixels tall. Allowed file formats are "
81
            "png, jpeg, gif. The file size "
82
            "should be max. 5 MB."
83
        ),
84
        upload_to="organisations/logos",
85
        blank=True,
86
    )
87
    url = models.URLField(
1✔
88
        blank=True,
89
        verbose_name="Organisation website",
90
        help_text=_(
91
            "Please enter " "a full url which " "starts with https:// " "or http://"
92
        ),
93
    )
94
    image = images_fields.ConfiguredImageField(
1✔
95
        "heroimage",
96
        verbose_name=_("Header image"),
97
        help_prefix=_("The image will be shown as a decorative background image."),
98
        upload_to="organisations/backgrounds",
99
        blank=True,
100
    )
101
    image_copyright = models.CharField(
1✔
102
        max_length=200,
103
        verbose_name=_("Header image copyright"),
104
        blank=True,
105
        help_text=_("Author, which is displayed in the header image."),
106
    )
107
    twitter_handle = models.CharField(
1✔
108
        max_length=100,
109
        blank=True,
110
        verbose_name="Twitter handle",
111
    )
112
    facebook_handle = models.CharField(
1✔
113
        max_length=100,
114
        blank=True,
115
        verbose_name="Facebook handle",
116
    )
117
    instagram_handle = models.CharField(
1✔
118
        max_length=100,
119
        blank=True,
120
        verbose_name="Instagram handle",
121
    )
122
    imprint = CKEditor5Field(
1✔
123
        verbose_name=_("Imprint"),
124
        help_text=_(
125
            "Please provide all the legally "
126
            "required information of your imprint. "
127
            "The imprint will be shown on a separate page."
128
        ),
129
        blank=True,
130
    )
131
    terms_of_use = CKEditor5Field(
1✔
132
        verbose_name=_("Terms of use"),
133
        help_text=_(
134
            "Please provide all the legally "
135
            "required information of your terms of use. "
136
            "The terms of use will be shown on a separate page."
137
        ),
138
        blank=True,
139
    )
140
    data_protection = CKEditor5Field(
1✔
141
        verbose_name=_("Data protection policy"),
142
        help_text=_(
143
            "Please provide all the legally "
144
            "required information of your data protection. "
145
            "The data protection policy will be shown on a "
146
            "separate page."
147
        ),
148
        blank=True,
149
    )
150
    netiquette = CKEditor5Field(
1✔
151
        verbose_name=_("Netiquette"),
152
        help_text=_(
153
            "Please provide a netiquette for the participants. "
154
            "The netiquette helps improving the climate of "
155
            "online discussions and supports the moderation."
156
        ),
157
        blank=True,
158
    )
159
    is_supporting = models.BooleanField(
1✔
160
        default=False,
161
        verbose_name=_("is a supporting organisation"),
162
        help_text=_(
163
            "For supporting organisations, the banner asking "
164
            "for donations is not displayed on their pages."
165
        ),
166
    )
167
    enable_geolocation = models.BooleanField(
1✔
168
        default=False,
169
        verbose_name=_("enable geolocation for projects"),
170
    )
171
    language = models.CharField(
1✔
172
        verbose_name=_("Default language for e-mails"),
173
        choices=settings.LANGUAGES,
174
        default=settings.DEFAULT_USER_LANGUAGE_CODE,
175
        max_length=4,
176
        help_text=_(
177
            "All e-mails to unregistered users (invites) will be sent "
178
            "in this language."
179
        ),
180
    )
181
    site = models.ForeignKey(Site, on_delete=models.SET_NULL, blank=True, null=True)
1✔
182

183
    def __str__(self):
1✔
184
        return self.name
×
185

186
    @cached_property
1✔
187
    def projects(self):
1✔
188
        return Project.objects.filter(
×
189
            organisation=self, is_archived=False, is_draft=False
190
        )
191

192
    def get_projects_list(self, user):
1✔
193
        projects = query.filter_viewable(self.projects, user)
×
194
        now = timezone.now()
×
195

196
        min_module_start = models.Min(
×
197
            "module__phase__start_date", filter=models.Q(module__is_draft=False)
198
        )
199
        max_module_end = models.Max(
×
200
            "module__phase__end_date", filter=models.Q(module__is_draft=False)
201
        )
202

203
        sorted_active_projects = (
×
204
            projects.annotate(project_start=min_module_start)
205
            .annotate(project_end=max_module_end)
206
            .filter(project_start__lte=now, project_end__gt=now)
207
            .order_by("project_end")
208
        )
209

210
        sorted_future_projects = (
×
211
            projects.annotate(project_start=min_module_start)
212
            .filter(models.Q(project_start__gt=now) | models.Q(project_start=None))
213
            .order_by("project_start")
214
        )
215

216
        sorted_past_projects = (
×
217
            projects.annotate(project_start=min_module_start)
218
            .annotate(project_end=max_module_end)
219
            .filter(project_end__lt=now)
220
            .order_by("-project_end")
221
        )
222

223
        return sorted_active_projects, sorted_future_projects, sorted_past_projects
×
224

225
    def has_initiator(self, user):
1✔
226
        return self.initiators.filter(id=user.id).exists()
×
227

228
    def has_org_member(self, user):
1✔
229
        return Member.objects.filter(
×
230
            member__id=user.id, organisation__id=self.id
231
        ).exists()
232

233
    def get_absolute_url(self):
1✔
234
        return reverse("organisation", kwargs={"organisation_slug": self.slug})
×
235

236
    def has_social_share(self):
1✔
237
        return self.twitter_handle or self.facebook_handle or self.instagram_handle
×
238

239
    def save(self, update_fields=None, *args, **kwargs):
1✔
240
        self.imprint = transforms.clean_html_field(self.imprint)
×
241
        if update_fields:
×
242
            update_fields = {"imprint"}.union(update_fields)
×
243
        super().save(update_fields=update_fields, *args, **kwargs)
×
244

245

246
class Member(models.Model):
1✔
247
    member = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
1✔
248
    organisation = models.ForeignKey(
1✔
249
        settings.A4_ORGANISATIONS_MODEL, on_delete=models.CASCADE
250
    )
251
    member_number = models.CharField(
1✔
252
        max_length=50,
253
        blank=True,
254
    )
255
    additional_info = JSONField(blank=True)
1✔
256

257
    class Meta:
1✔
258
        unique_together = [("member", "organisation")]
1✔
259

260
    def __str__(self):
1✔
261
        return "{}_{}".format(self.organisation, self.member)
×
262

263

264
class OrganisationTermsOfUse(models.Model):
1✔
265
    user = models.ForeignKey(
1✔
266
        settings.AUTH_USER_MODEL,
267
        on_delete=models.CASCADE,
268
        editable=False,
269
    )
270
    organisation = models.ForeignKey(
1✔
271
        settings.A4_ORGANISATIONS_MODEL,
272
        on_delete=models.CASCADE,
273
        editable=False,
274
    )
275
    has_agreed = models.BooleanField(
1✔
276
        default=False,
277
    )
278

279
    class Meta:
1✔
280
        unique_together = [("user", "organisation")]
1✔
281

282
    def __str__(self):
1✔
283
        return "{}_{}".format(self.organisation, self.user)
×
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