• 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

69.23
/apps/users/models.py
1
from django.conf import settings
1✔
2
from django.contrib.auth import models as auth_models
1✔
3
from django.core import validators
1✔
4
from django.db import models
1✔
5
from django.templatetags.static import static
1✔
6
from django.urls import reverse
1✔
7
from django.utils import timezone
1✔
8
from django.utils.functional import cached_property
1✔
9
from django.utils.translation import gettext_lazy as _
1✔
10

11
from adhocracy4.images.fields import ConfiguredImageField
1✔
12
from adhocracy4.projects.enums import Access
1✔
13
from adhocracy4.projects.models import Project
1✔
14
from apps.organisations.models import OrganisationTermsOfUse
1✔
15

16
from . import USERNAME_INVALID_MESSAGE
1✔
17
from . import USERNAME_REGEX
1✔
18

19

20
class User(auth_models.AbstractBaseUser, auth_models.PermissionsMixin):
1✔
21
    username = models.CharField(
1✔
22
        _("username"),
23
        max_length=60,
24
        unique=True,
25
        help_text=_(
26
            "Required. 60 characters or fewer. Letters, digits, spaces and "
27
            "@/./+/-/_ only."
28
        ),
29
        validators=[
30
            validators.RegexValidator(
31
                USERNAME_REGEX, USERNAME_INVALID_MESSAGE, "invalid"
32
            )
33
        ],
34
        error_messages={
35
            "unique": _("A user with that username already exists."),
36
            "used_as_email": _("This username is invalid."),
37
        },
38
    )
39

40
    email = models.EmailField(
1✔
41
        _("Email address"),
42
    )
43

44
    is_staff = models.BooleanField(
1✔
45
        _("staff status"),
46
        default=False,
47
        help_text=_("Designates whether the user can log into this admin site."),
48
    )
49

50
    is_active = models.BooleanField(
1✔
51
        _("active"),
52
        default=True,
53
        help_text=_(
54
            "Designates whether this user should be treated as active. "
55
            "Unselect this instead of deleting accounts."
56
        ),
57
    )
58

59
    date_joined = models.DateTimeField(editable=False, default=timezone.now)
1✔
60

61
    get_notifications = models.BooleanField(
1✔
62
        verbose_name=_("Send me email notifications"),
63
        default=True,
64
        help_text=_(
65
            "Designates whether you want to receive notifications. "
66
            "Unselect if you do not want to receive notifications."
67
        ),
68
    )
69

70
    get_newsletters = models.BooleanField(
1✔
71
        verbose_name=_("I would like to receive further information"),
72
        default=False,
73
        help_text=_(
74
            "Projects you are following can send you "
75
            "additional information via email."
76
        ),
77
    )
78

79
    bio = models.TextField(
1✔
80
        blank=True,
81
        max_length=255,
82
        verbose_name=_("Biography"),
83
        help_text=_("Tell us about yourself in 255 characters!"),
84
    )
85

86
    twitter_handle = models.CharField(
1✔
87
        blank=True,
88
        max_length=15,
89
        verbose_name=_("Twitter handle"),
90
    )
91

92
    facebook_handle = models.CharField(
1✔
93
        blank=True,
94
        max_length=50,
95
        verbose_name=_("Facebook name"),
96
        help_text=_(
97
            "Your facebook name is the last part of the URL, "
98
            "when you access your profile."
99
        ),
100
    )
101

102
    homepage = models.URLField(
1✔
103
        blank=True,
104
        max_length=50,
105
        verbose_name=_("Homepage"),
106
    )
107

108
    _avatar = ConfiguredImageField(
1✔
109
        "avatar",
110
        upload_to="users/images",
111
        blank=True,
112
        verbose_name=_("Avatar picture"),
113
    )
114

115
    language = models.CharField(
1✔
116
        verbose_name=_("Your preferred language"),
117
        choices=settings.LANGUAGES,
118
        default=settings.DEFAULT_USER_LANGUAGE_CODE,
119
        max_length=4,
120
        help_text=_(
121
            "Specify your preferred language for the user interface "
122
            "and the notifications of the platform."
123
        ),
124
    )
125

126
    objects = auth_models.UserManager()
1✔
127

128
    USERNAME_FIELD = "username"
1✔
129
    REQUIRED_FIELDS = ["email"]
1✔
130

131
    def get_projects_follow_list(self, exclude_private_projects=False):
1✔
132
        projects = Project.objects.filter(
×
133
            follow__creator=self, follow__enabled=True, is_draft=False
134
        ).select_related("organisation")
135
        if exclude_private_projects:
×
136
            projects = projects.exclude(models.Q(access=Access.PRIVATE))
×
137

138
        now = timezone.now()
×
139

140
        sorted_active_projects = (
×
141
            projects.annotate(project_start=models.Min("module__phase__start_date"))
142
            .annotate(project_end=models.Max("module__phase__end_date"))
143
            .filter(project_start__lte=now, project_end__gt=now)
144
            .order_by("project_end")
145
        )
146

147
        sorted_future_projects = (
×
148
            projects.annotate(project_start=models.Min("module__phase__start_date"))
149
            .filter(models.Q(project_start__gt=now) | models.Q(project_start=None))
150
            .order_by("project_start")
151
        )
152

153
        sorted_past_projects = (
×
154
            projects.annotate(project_start=models.Min("module__phase__start_date"))
155
            .annotate(project_end=models.Max("module__phase__end_date"))
156
            .filter(project_end__lt=now)
157
            .order_by("project_start")
158
        )
159

160
        return sorted_active_projects, sorted_future_projects, sorted_past_projects
×
161

162
    @cached_property
1✔
163
    def organisations(self):
1✔
164
        return self.organisation_set.all()
×
165

166
    @cached_property
1✔
167
    def avatar(self):
1✔
168
        if self._avatar:
×
169
            return self._avatar
×
170

171
    @cached_property
1✔
172
    def avatar_fallback(self):
1✔
173
        number = self.pk % 5
×
174
        return static("images/avatar-{0:02d}.svg".format(number))
×
175

176
    @cached_property
1✔
177
    def avatar_fallback_png(self):
1✔
178
        number = self.pk % 5
×
179
        return static("images/avatar-{0:02d}.png".format(number))
×
180

181
    def get_short_name(self):
1✔
182
        return self.username
×
183

184
    def get_full_name(self):
1✔
185
        full_name = "%s <%s>" % (self.username, self.email)
×
186
        return full_name.strip()
×
187

188
    def get_absolute_url(self):
1✔
189
        return reverse("profile", args=[str(self.username)])
×
190

191
    def has_agreed_on_org_terms(self, organisation):
1✔
192
        return OrganisationTermsOfUse.objects.filter(
×
193
            user=self, organisation=organisation, has_agreed=True
194
        ).exists()
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