models.py 6.63 KB
Newer Older
1
from django.utils.translation import gettext_lazy as _
2
from django.utils.timezone import utc, now
3
from django.db import models
4
5
6
7
8
9
10
from django.urls import reverse
from django.conf import settings
from backend.models import Person, AM
from backend import const
from backend import permissions
import os
import datetime
11

12
13
14
15
16

class Process(models.Model):
    """
    A process through which a person gets a new status

17
    There can be more than one Process per Person, but only one of them can be
18
19
    active at any one time. This is checked during maintenance.
    """
20
    person = models.ForeignKey(Person, related_name="legacy_processes", on_delete=models.CASCADE)
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

    applying_as = models.CharField("original status", max_length=20, null=False,
                                   choices=[x[1:3] for x in const.ALL_STATUS])
    applying_for = models.CharField("target status", max_length=20, null=False,
                                    choices=[x[1:3] for x in const.ALL_STATUS])
    progress = models.CharField(max_length=20, null=False,
                                choices=[x[1:3] for x in const.ALL_PROGRESS])

    # This is NULL until one gets a manager
    manager = models.ForeignKey(AM, related_name="+", null=True, blank=True, on_delete=models.PROTECT)

    advocates = models.ManyToManyField(Person, related_name="+", blank=True,
                                       limit_choices_to={"status__in": (const.STATUS_DD_U, const.STATUS_DD_NU)})

    # True if progress NOT IN (PROGRESS_DONE, PROGRESS_CANCELLED)
    is_active = models.BooleanField(null=False, default=False)

    closed = models.DateTimeField(
            null=True, blank=True, help_text=_("Date the process was closed, or NULL if still open"))

    archive_key = models.CharField("mailbox archive key", max_length=128, null=False, unique=True)

    def save(self, *args, **kw):
        if not self.archive_key:
            ts = now().strftime("%Y%m%d%H%M%S")
46
47
            if self.person.ldap_fields.uid:
                self.archive_key = "-".join((ts, self.applying_for, self.person.ldap_fields.uid))
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
            else:
                self.archive_key = "-".join((ts, self.applying_for, self.person.email))
        super(Process, self).save(*args, **kw)

    def __str__(self):
        return "{} to become {} ({})".format(
            str(self.person),
            const.ALL_STATUS_DESCS.get(self.applying_for, self.applying_for),
            const.ALL_PROGRESS_DESCS.get(self.progress, self.progress),
        )

    def __repr__(self):
        return "{} {}->{}".format(
            self.person.lookup_key,
            self.person.status,
            self.applying_for)

    def get_absolute_url(self):
66
        return reverse("legacy:process", kwargs=dict(key=self.lookup_key))
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143

    def get_admin_url(self):
        return reverse("admin:backend_process_change", args=[self.pk])

    @property
    def a_link(self):
        from django.utils.safestring import mark_safe
        from django.utils.html import conditional_escape
        return mark_safe("<a href='{}'>→ {}</a>".format(
            conditional_escape(self.get_absolute_url()),
            conditional_escape(const.ALL_STATUS_DESCS[self.applying_for])))

    @property
    def lookup_key(self):
        """
        Return a key that can be used to look up this process in the database
        using Process.lookup.

        Currently, this is the email if the process is active, else the id.
        """
        return str(self.id)

    @classmethod
    def lookup(cls, key):
        # Key can either be a Process ID or a person's lookup key
        if key.isdigit():
            try:
                return cls.objects.get(id=int(key))
            except cls.DoesNotExist:
                return None
        else:
            # If a person's lookup key is used, and there is only one active
            # process, return that one. Else, return the most recent process.
            p = Person.lookup(key)
            if p is None:
                return None

            # If we reach here, either we have one process, or a new process
            # has been added # changed since the URL was generated. We have an
            # ambiguous situation, which we handle blissfully arbitrarily
            try:
                from django.db.models import Max
                return p.legacy_processes.annotate(last_change=Max("log__logdate")).order_by("-last_change")[0]
            except IndexError:
                return None

    @classmethod
    def lookup_or_404(cls, key):
        from django.http import Http404
        res = cls.lookup(key)
        if res is not None:
            return res
        raise Http404

    @property
    def mailbox_file(self):
        """
        The pathname of the archival mailbox, or None if it does not exist
        """
        PROCESS_MAILBOX_DIR_OLD = getattr(settings, "PROCESS_MAILBOX_DIR_OLD", "/srv/nm.debian.org/mbox/applicants/")
        fname = os.path.join(PROCESS_MAILBOX_DIR_OLD, self.archive_key) + ".mbox"
        if os.path.exists(fname):
            return fname
        return None

    @property
    def mailbox_mtime(self):
        """
        The mtime of the archival mailbox, or None if it does not exist
        """
        fname = self.mailbox_file
        if fname is None:
            return None
        return datetime.datetime.utcfromtimestamp(os.path.getmtime(fname)).replace(tzinfo=utc)

    @property
    def archive_email(self):
144
145
        if self.person.ldap_fields.uid:
            key = self.person.ldap_fields.uid
146
147
148
149
150
151
152
153
        else:
            key = self.person.email.replace("@", "=")
        return "archive-{}@nm.debian.org".format(key)

    def permissions_of(self, visitor):
        """
        Compute which LegacyProcessVisitorPermissions \a visitor has over this process
        """
154
155
156
157
        if visitor.is_authenticated:
            return permissions.LegacyProcessVisitorPermissions(self, visitor)
        else:
            return permissions.LegacyProcessVisitorPermissions(self, None)
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176


class Log(models.Model):
    """
    A log entry about anything that happened during a process
    """
    changed_by = models.ForeignKey(Person, related_name="+", null=True, on_delete=models.CASCADE)
    process = models.ForeignKey(Process, related_name="log", on_delete=models.CASCADE)

    # Copied from Process when the log entry is created
    progress = models.CharField(max_length=20, null=False,
                                choices=[(x.tag, x.ldesc) for x in const.ALL_PROGRESS])

    is_public = models.BooleanField(default=False, null=False)
    logdate = models.DateTimeField(null=False, default=now)
    logtext = models.TextField(null=False, blank=True, default="")

    def __str__(self):
        return "{}: {}".format(self.logdate, self.logtext)