source: asadb/groups/models.py @ d7dcfee

space-accessstablestage
Last change on this file since d7dcfee was d7dcfee, checked in by Alex Dehnert <adehnert@…>, 14 years ago

Make recognition_date a datetime field

There are two motivations for this:

  • Having the time a group filled out the startup form is kinda useful, and the group object stores when the startup form is filled out in the recognition_date field, until the group is recognized. If that field is a datetime field, then that data becomes available.
  • Apparently reversion has a bug whereby it will store whatever is assigned to the field, and then validate it on the way out. If you assign a datetime to a normal date field, then this will cause a validation error when you load it from the database. Since we were storing a datetime when doing group startup, this was occasionally causing breakage. (See Trac #47.)
  • Property mode set to 100644
File size: 12.8 KB
Line 
1from django.db import models
2from django.contrib.auth.models import User
3
4import datetime
5
6import settings
7
8# Create your models here.
9
10class ActiveGroupManager(models.Manager):
11    def get_query_set(self, ):
12        return super(ActiveGroupManager, self).get_query_set().filter(
13            group_status__slug='active',
14        )
15
16class Group(models.Model):
17    name = models.CharField(max_length=100)
18    abbreviation = models.CharField(max_length=10, blank=True)
19    description = models.TextField()
20    activity_category = models.ForeignKey('ActivityCategory', null=True, blank=True, )
21    group_class = models.ForeignKey('GroupClass')
22    group_status = models.ForeignKey('GroupStatus')
23    group_funding = models.ForeignKey('GroupFunding', null=True, blank=True, )
24    website_url = models.URLField()
25    constitution_url = models.CharField(max_length=200, blank=True)
26    meeting_times = models.TextField(blank=True)
27    advisor_name = models.CharField(max_length=100, blank=True)
28    num_undergrads = models.IntegerField(null=True, blank=True, )
29    num_grads = models.IntegerField(null=True, blank=True, )
30    num_community = models.IntegerField(null=True, blank=True, )
31    num_other = models.IntegerField(null=True, blank=True, )
32    group_email = models.EmailField(blank=True, )
33    officer_email = models.EmailField()
34    main_account_id = models.IntegerField(null=True, blank=True, )
35    funding_account_id = models.IntegerField(null=True, blank=True, )
36    athena_locker = models.CharField(max_length=20, blank=True)
37    recognition_date = models.DateTimeField()
38    update_date = models.DateTimeField(editable=False, )
39    updater = models.CharField(max_length=30, editable=False, null=True, ) # match Django username field
40    _updater_set = False
41
42    objects = models.Manager()
43    active_groups = ActiveGroupManager()
44
45    def update_string(self, ):
46        updater = self.updater or "unknown"
47        return "%s by %s" % (self.update_date.strftime(settings.DATETIME_FORMAT_PYTHON), updater, )
48
49    def set_updater(self, who):
50        if hasattr(who, 'username'):
51            self.updater = who.username
52        else:
53            self.updater = who
54        self._updater_set = True
55
56    def save(self, ):
57        if not self._updater_set:
58            self.updater = None
59        self.update_date = datetime.datetime.now()
60        super(Group, self).save()
61
62    def viewable_notes(self, user):
63        return GroupNote.viewable_notes(self, user)
64
65    def officers(self, role=None, person=None, as_of="now",):
66        """Get the set of people holding some office.
67
68        If None is passed for role, person, or as_of, that field will not
69        be constrained. If as_of is "now" (default) the status will be
70        required to be current. If any of the three parameters are set
71        to another value, the corresponding filter will be applied.
72        """
73        office_holders = OfficeHolder.objects.filter(group=self,)
74        if role:
75            if isinstance(role, str):
76                office_holders = office_holders.filter(role__slug=role)
77            else:
78                office_holders = office_holders.filter(role=role)
79        if person:
80            office_holders = office_holders.filter(person=person)
81        if as_of:
82            if as_of == "now": as_of = datetime.datetime.now()
83            office_holders = office_holders.filter(start_time__lte=as_of, end_time__gte=as_of)
84        return office_holders
85
86    def __str__(self, ):
87        return self.name
88
89    class Meta:
90        ordering = ('name', )
91        permissions = (
92            ('view_group_private_info', 'View private group information'),
93            # ability to update normal group info or people
94            # this is weaker than change_group, which is the built-in
95            # permission that controls the admin interface
96            ('admin_group', 'Administer basic group information'),
97            ('view_signatories', 'View signatory information for all groups'),
98            ('recognize_nge', 'Recognize Non-Group Entity'),
99            ('recognize_group', 'Recognize groups'),
100        )
101
102
103GROUP_STARTUP_STAGE_SUBMITTED = 10
104GROUP_STARTUP_STAGE_APPROVED = 20
105GROUP_STARTUP_STAGE_REJECTED = -10
106GROUP_STARTUP_STAGE = (
107    (GROUP_STARTUP_STAGE_SUBMITTED,     'submitted'),
108    (GROUP_STARTUP_STAGE_APPROVED,      'approved'),
109    (GROUP_STARTUP_STAGE_REJECTED,      'rejected'),
110)
111
112class GroupStartup(models.Model):
113    group = models.ForeignKey(Group)
114    stage = models.IntegerField(choices=GROUP_STARTUP_STAGE)
115    submitter = models.CharField(max_length=30, editable=False, )
116    create_officer_list = models.BooleanField()
117    create_group_list = models.BooleanField()
118    create_athena_locker = models.BooleanField()
119    president_name = models.CharField(max_length=50)
120    president_kerberos = models.CharField(max_length=8)
121    treasurer_name = models.CharField(max_length=50)
122    treasurer_kerberos = models.CharField(max_length=8)
123
124
125class GroupNote(models.Model):
126    author = models.CharField(max_length=30, ) # match Django username field
127    timestamp = models.DateTimeField(default=datetime.datetime.now, editable=False, )
128    body = models.TextField()
129    acl_read_group = models.BooleanField(default=True, help_text='Can the group read this note')
130    acl_read_offices = models.BooleanField(default=True, help_text='Can "offices" that interact with groups (SAO, CAC, and funding boards) read this note')
131    group = models.ForeignKey(Group)
132
133    def __str__(self, ):
134        return "Note by %s on %s" % (self.author, self.timestamp, )
135
136    @classmethod
137    def viewable_notes(cls, group, user):
138        notes = cls.objects.filter(group=group)
139        if not user.has_perm('groups.view_note_all'):
140            q = models.Q(pk=0)
141            if user.has_perm('groups.view_note_group', group):
142                q |= models.Q(acl_read_group=True)
143            if user.has_perm('groups.view_note_office'):
144                q |= models.Q(acl_read_offices=True)
145            notes = notes.filter(q)
146        return notes
147
148    class Meta:
149        permissions = (
150            ('view_note_group',     'View notes intended for the group to see', ),
151            ('view_note_office',    'View notes intended for "offices" to see', ),
152            ('view_note_all',       'View all notes', ),
153        )
154
155
156class OfficerRole(models.Model):
157    UNLIMITED = 10000
158
159    display_name = models.CharField(max_length=50)
160    slug = models.SlugField()
161    description = models.TextField()
162    max_count = models.IntegerField(default=UNLIMITED, help_text='Maximum number of holders of this role. Use %d for no limit.' % UNLIMITED)
163    require_student = models.BooleanField(default=False)
164    grant_user = models.ForeignKey(User, null=True, blank=True,
165        limit_choices_to={ 'username__endswith': '@SYSTEM'})
166    publicly_visible = models.BooleanField(default=True, help_text='Can everyone see the holders of this office.')
167
168    def max_count_str(self, ):
169        if self.max_count == self.UNLIMITED:
170            return "unlimited"
171        else:
172            return str(self.max_count)
173
174    def __str__(self, ):
175        return self.display_name
176
177    @classmethod
178    def getGrantUsers(cls, roles):
179        ret = set([role.grant_user for role in roles])
180        if None in ret: ret.remove(None)
181        return ret
182
183    @classmethod
184    def retrieve(cls, slug, ):
185        return cls.objects.get(slug=slug)
186
187
188class OfficeHolder_CurrentManager(models.Manager):
189    def get_query_set(self, ):
190        return super(OfficeHolder_CurrentManager, self).get_query_set().filter(
191            start_time__lte=datetime.datetime.now,
192            end_time__gte=datetime.datetime.now,
193        )
194
195class OfficeHolder(models.Model):
196    EXPIRE_OFFSET   = datetime.timedelta(seconds=1)
197    END_NEVER       = datetime.datetime.max
198
199    person = models.CharField(max_length=30)
200    role = models.ForeignKey('OfficerRole')
201    group = models.ForeignKey('Group')
202    start_time = models.DateTimeField(default=datetime.datetime.now)
203    end_time = models.DateTimeField(default=datetime.datetime.max)
204
205    objects = models.Manager()
206    current_holders = OfficeHolder_CurrentManager()
207
208    def expire(self, ):
209        self.end_time = datetime.datetime.now()-self.EXPIRE_OFFSET
210        self.save()
211
212    def __str__(self, ):
213        return "<OfficeHolder: person=%s, role=%s, group=%s, start_time=%s, end_time=%s>" % (
214            self.person, self.role, self.group, self.start_time, self.end_time, )
215
216    def __repr__(self, ):
217        return str(self)
218
219
220class PerGroupAuthz:
221    supports_anonymous_user = True
222    supports_inactive_user = True
223    supports_object_permissions = True
224
225    def authenticate(self, username=None, password=None, ):
226        return None # we don't do authn
227    def get_user(user_id, ):
228        return None # we don't do authn
229
230    def has_perm(self, user_obj, perm, obj=None, ):
231        print "Checking user %s for perm %s on obj %s" % (user_obj, perm, obj)
232        if not user_obj.is_active:
233            return False
234        if not user_obj.is_authenticated():
235            return False
236        if obj is None:
237            return False
238        # Great, we're active, authenticated, and not in a recursive call
239        # Check that we've got a reasonable object
240        if getattr(user_obj, 'is_system', False):
241            return False
242        if isinstance(obj, Group):
243            # Now we can do the real work
244            holders = obj.officers(person=user_obj.username).select_related('role__grant_user')
245            sys_users = OfficerRole.getGrantUsers([holder.role for holder in holders])
246            for sys_user in sys_users:
247                sys_user.is_system = True
248                if sys_user.has_perm(perm):
249                    print "While checking user %s for perm %s on obj %s: implicit user %s has perms" % (user_obj, perm, obj, sys_user, )
250                    return True
251        print "While checking user %s for perm %s on obj %s: no perms found (implicit: %s)" % (user_obj, perm, obj, sys_users)
252        return False
253
254
255
256class ActivityCategory(models.Model):
257    name = models.CharField(max_length=50)
258
259    def __str__(self, ):
260        return self.name
261
262    class Meta:
263        verbose_name_plural = "activity categories"
264
265
266class GroupClass(models.Model):
267    name = models.CharField(max_length=50)
268    slug = models.SlugField(unique=True, )
269    description = models.TextField()
270    gets_publicity = models.BooleanField(help_text="Gets publicity resources such as FYSM or Activities Midway")
271
272    def __str__(self, ):
273        return self.name
274
275    class Meta:
276        verbose_name_plural = "group classes"
277
278
279class GroupStatus(models.Model):
280    name = models.CharField(max_length=50)
281    slug = models.SlugField(unique=True, )
282    description = models.TextField()
283    is_active = models.BooleanField(default=True, help_text="This status represents an active group")
284
285    def __str__(self, ):
286        active = ""
287        if not self.is_active:
288            active = " (inactive)"
289        return "%s%s" % (self.name, active, )
290
291    class Meta:
292        verbose_name_plural= "group statuses"
293
294
295class GroupFunding(models.Model):
296    name = models.CharField(max_length=50)
297    slug = models.SlugField(unique=True, )
298    contact_email = models.EmailField()
299    funding_list = models.CharField(max_length=32, blank=True, help_text="List that groups receiving funding emails should be on. The database will attempt to make sure that ONLY those groups are on it.")
300
301    def __str__(self, ):
302        return "%s (%s)" % (self.name, self.contact_email, )
303
304
305class AthenaMoiraAccount_ActiveManager(models.Manager):
306    def get_query_set(self, ):
307        return super(AthenaMoiraAccount_ActiveManager, self).get_query_set().filter(del_date=None)
308
309class AthenaMoiraAccount(models.Model):
310    username = models.CharField(max_length=8)
311    mit_id = models.CharField(max_length=15)
312    first_name      = models.CharField(max_length=45)
313    last_name       = models.CharField(max_length=45)
314    account_class   = models.CharField(max_length=10)
315    mutable         = models.BooleanField(default=True)
316    add_date        = models.DateField(help_text="Date when this person was added to the dump.", )
317    del_date        = models.DateField(help_text="Date when this person was removed from the dump.", blank=True, null=True, )
318    mod_date        = models.DateField(help_text="Date when this person's record was last changed.", blank=True, null=True, )
319
320    objects = models.Manager()
321    active_accounts = AthenaMoiraAccount_ActiveManager()
322
323    def is_student(self, ):
324        # XXX: Is this... right?
325        return self.account_class == 'G' or self.account_class.isdigit()
326
327    def __str__(self, ):
328        if self.mutable:
329            mutable_str = ""
330        else:
331            mutable_str = " (immutable)"
332        return "<AthenaMoiraAccount: username=%s name='%s, %s' account_class=%s%s>" % (
333            self.username, self.last_name, self.first_name,
334            self.account_class, mutable_str,
335        )
336
337    def __repr__(self, ):
338        return str(self)
339
340    class Meta:
341        verbose_name = "Athena (Moira) account"
Note: See TracBrowser for help on using the repository browser.