@@ -242,6 +242,150 @@ def get_sig_admins(cls, group):
242242 ).distinct ()
243243
244244
245+ class Committee (models .Model ):
246+ """
247+ Committee model for makerspace committees.
248+
249+ Committees are organizational structures that contain Special Interest Groups (SIGs).
250+ Each committee can have one or more chairs who can create and manage SIGs within that committee.
251+ """
252+
253+ name = models .CharField (
254+ max_length = 200 ,
255+ unique = True ,
256+ help_text = "Name of the committee" ,
257+ )
258+ description = models .TextField (
259+ blank = True ,
260+ help_text = "Description of the committee's purpose and responsibilities" ,
261+ )
262+ is_active = models .BooleanField (
263+ default = True ,
264+ help_text = "Whether this committee is currently active" ,
265+ )
266+ created_at = models .DateTimeField (auto_now_add = True )
267+ updated_at = models .DateTimeField (auto_now = True )
268+
269+ class Meta :
270+ ordering = ["name" ]
271+ verbose_name = "Committee"
272+ verbose_name_plural = "Committees"
273+ indexes = [
274+ models .Index (fields = ["is_active" , "name" ]),
275+ ]
276+
277+ def __str__ (self ) -> str :
278+ return self .name
279+
280+ @property
281+ def sig_count (self ):
282+ """Get the number of SIGs in this committee."""
283+ return self .sigs .count ()
284+
285+
286+ class CommitteeChair (models .Model ):
287+ """
288+ Tracks which users are chairs of which committees.
289+
290+ Committee Chairs can create and manage Special Interest Groups (SIGs)
291+ within their committee.
292+ """
293+
294+ user = models .ForeignKey (
295+ settings .AUTH_USER_MODEL ,
296+ on_delete = models .CASCADE ,
297+ related_name = "committee_chair_roles" ,
298+ help_text = "User who is a chair of this committee" ,
299+ )
300+ committee = models .ForeignKey (
301+ Committee ,
302+ on_delete = models .CASCADE ,
303+ related_name = "chairs" ,
304+ help_text = "Committee this user chairs" ,
305+ )
306+ is_active = models .BooleanField (
307+ default = True ,
308+ help_text = "Is this chair role active?" ,
309+ )
310+ created_at = models .DateTimeField (auto_now_add = True )
311+ updated_at = models .DateTimeField (auto_now = True )
312+
313+ class Meta :
314+ unique_together = [["user" , "committee" ]]
315+ ordering = ["committee" , "user" ]
316+ indexes = [
317+ models .Index (fields = ["user" , "is_active" ]),
318+ models .Index (fields = ["committee" , "is_active" ]),
319+ ]
320+ verbose_name = "Committee Chair"
321+ verbose_name_plural = "Committee Chairs"
322+
323+ def __str__ (self ) -> str :
324+ return f"{ self .user .username } - { self .committee .name } "
325+
326+ @classmethod
327+ def is_committee_chair (cls , user , committee ):
328+ """Check if a user is a chair of a specific committee."""
329+ if not user or not user .is_authenticated or not committee :
330+ return False
331+ return cls .objects .filter (user = user , committee = committee , is_active = True ).exists ()
332+
333+ @classmethod
334+ def get_user_committees (cls , user ):
335+ """Get all committees that a user chairs."""
336+ if not user or not user .is_authenticated :
337+ return Committee .objects .none ()
338+ return Committee .objects .filter (
339+ chairs__user = user , chairs__is_active = True
340+ ).distinct ()
341+
342+ @classmethod
343+ def get_committee_chairs (cls , committee ):
344+ """Get all chair users for a specific committee."""
345+ from django .contrib .auth import get_user_model
346+
347+ User = get_user_model ()
348+ if not committee :
349+ return User .objects .none ()
350+ return User .objects .filter (
351+ committee_chair_roles__committee = committee , committee_chair_roles__is_active = True
352+ ).distinct ()
353+
354+
355+ class SIGCommittee (models .Model ):
356+ """
357+ Links a SIG (Group) to a Committee.
358+
359+ This model creates the relationship between Special Interest Groups (represented as Django Groups)
360+ and Committees. Each SIG belongs to one committee.
361+ """
362+
363+ group = models .OneToOneField (
364+ Group ,
365+ on_delete = models .CASCADE ,
366+ related_name = "committee_membership" ,
367+ help_text = "SIG (Group) that belongs to this committee" ,
368+ )
369+ committee = models .ForeignKey (
370+ Committee ,
371+ on_delete = models .CASCADE ,
372+ related_name = "sigs" ,
373+ help_text = "Committee this SIG belongs to" ,
374+ )
375+ created_at = models .DateTimeField (auto_now_add = True )
376+ updated_at = models .DateTimeField (auto_now = True )
377+
378+ class Meta :
379+ verbose_name = "SIG Committee Membership"
380+ verbose_name_plural = "SIG Committee Memberships"
381+ indexes = [
382+ models .Index (fields = ["committee" , "group" ]),
383+ ]
384+
385+ def __str__ (self ) -> str :
386+ return f"{ self .group .name } - { self .committee .name } "
387+
388+
245389class UserRegistrationToken (models .Model ):
246390 """
247391 One-time use token for user registration via QR code.
0 commit comments