def setUpTestData(cls): """Set up valid and invalid schemas for the test case once when the test case class is being prepared to run. Additionally, initialize a JSONSchemaValidator object for use in the individual unit tests. """ cls.valid_schema = { 'schema': 'http://json-schema.org/draft-07/schema#', 'type': 'string', 'minLength': 1 } cls.invalid_schema = { 'schema': 'http://json-schema.org/draft-07/schema#', 'type': 'string', 'minLength': -1 } cls.validator = JSONSchemaValidator(cls.valid_schema)
class Announcement(models.Model): """A Django database model which represents a club announcement or update. In the PostgreSQL database, an announcement has a title, body of content, and the date and time that it was created. The schema used to validate an announcement's body content, as represented in JSON, is defined above by the global ANNOUNCEMENT_BODY_FIELD_JSON_SCHEMA. Attributes: # noqa title: A CharField containing the announcement's title. body: A JSONField containing a JSON representation of the announcement's body or content. created: A DateTimeField that contains the date and time that the announcement was created. """ title = models.CharField( max_length=250, null=False, blank=False, default='', editable=True, unique=False, verbose_name='Announcement Title' ) body = models.JSONField( default=list, validators=[JSONSchemaValidator(limit_value=ANNOUNCEMENT_BODY_FIELD_JSON_SCHEMA)], null=False, blank=False, editable=True, unique=False, verbose_name='Announcement Body' ) created = models.DateTimeField( auto_now_add=True, null=False, blank=True, editable=False, unique=False, verbose_name='Announcement Creation Time/Date' ) def __str__(self): """Defines the string representation of the Announcement class. The string representation of an Announcement class instance only contains the title of the announcement. Returns: A string containing the announcement's title. """ return self.title class Meta: """This class contains meta-options for the Announcement model. Attributes: # noqa ordering: A list of fields to order Announcement objects by. As-is, they are ordered by the date/time they were created and in descending order (i.e., the newest Announcement appears first and the oldest appears last). """ ordering = ['-created']
class Webhook(models.Model): """Model of webhooks. If webhook has not null project field -- it's project webhook """ organization = models.ForeignKey('organizations.Organization', on_delete=models.CASCADE, related_name='webhooks') project = models.ForeignKey('projects.Project', null=True, on_delete=models.CASCADE, related_name='webhooks', default=None) url = models.URLField(_('URL of webhook'), max_length=2048, help_text=_('URL of webhook')) send_payload = models.BooleanField( _("does webhook send the payload"), default=True, help_text=('If value is False send only action'), db_index=True) send_for_all_actions = models.BooleanField( _("Use webhook for all actions"), default=True, help_text= 'If value is False - used only for actions from WebhookAction', db_index=True) headers = models.JSONField( _("request extra headers of webhook"), validators=[JSONSchemaValidator(HEADERS_SCHEMA)], default=dict, help_text='Key Value Json of headers', ) is_active = models.BooleanField( _("is webhook active"), default=True, help_text=('If value is False the webhook is disabled'), db_index=True) created_at = models.DateTimeField(_('created at'), auto_now_add=True, help_text=_('Creation time'), db_index=True) updated_at = models.DateTimeField(_('updated at'), auto_now=True, help_text=_('Last update time'), db_index=True) def get_actions(self): return WebhookAction.objects.filter(webhook=self).values_list( 'action', flat=True) def validate_actions(self, actions): actions_meta = [WebhookAction.ACTIONS[action] for action in actions] if self.project and any( (meta.get('organization-only') for meta in actions_meta)): raise ValidationError( "Project webhook can't contain organization-only action.") return actions def set_actions(self, actions): if not actions: actions = set() actions = set(actions) old_actions = set(self.get_actions()) for new_action in list(actions - old_actions): WebhookAction.objects.create(webhook=self, action=new_action) WebhookAction.objects.filter(webhook=self, action__in=(old_actions - actions)).delete() def has_permission(self, user): return self.organization.has_user(user) class Meta: db_table = 'webhook'
class MentorContactForm(ContactFormBase): """A concrete contact form model, intended for prospective mentors, which includes additional information such as the number of students to mentor, information about the mentor's background and field of expertise, and the availability of the prospective mentor to meet with club members. """ students = models.PositiveSmallIntegerField( null=False, blank=False, default=1, editable=True, unique=False, verbose_name='Number of Students to Mentor', validators=[ MinValueValidator(1, 'Number of students too small.'), MaxValueValidator(6, 'Number of students too large.'), ], ) field_type = models.CharField( max_length=60, null=False, blank=False, default='', editable=True, unique=False, verbose_name='Type of Field', ) field_name = models.CharField( max_length=200, null=False, blank=False, default='', editable=True, unique=False, verbose_name='Field/Sector Name', ) field_description = models.TextField( null=True, blank=True, default='', editable=True, unique=False, verbose_name='Field/Sector Name', ) availability_start = models.DateField( null=False, blank=False, editable=True, unique=False, verbose_name='Start of Mentorship Availability', ) availability_end = models.DateField( null=True, blank=True, editable=True, unique=False, verbose_name='End of Mentorship Availability', ) meeting_information = models.JSONField( default=list, validators=[ JSONSchemaValidator( limit_value=MENTOR_MEETING_INFORMATION_FIELD_SCHEMA) ], null=False, blank=False, editable=True, unique=False, verbose_name='Weekly Meeting Availability', ) weekly_minutes = models.PositiveSmallIntegerField( null=False, blank=False, default=1, editable=True, unique=False, verbose_name='Minutes of Availability Per Week', validators=[ MinValueValidator(1, 'Number of minutes too small.'), ], ) class Meta: """Defines the long-form name to label MentorContactForm objects. """ verbose_name = 'Mentor Contact Form'
class GuestSpeakerContactForm(ContactFormBase): """A concrete contact form model, intended for prospective guest speakers, which includes additional information such as the presentation topic and length, speaker availability, accommodations needed, and consent fields for audio/video recordings of the meeting. """ topic = models.CharField( max_length=250, null=False, blank=False, default='', editable=True, unique=False, verbose_name='Presentation Topic', ) availability = models.JSONField( default=list, validators=[ JSONSchemaValidator( limit_value=GUEST_SPEAKER_AVAILABILITY_FIELD_SCHEMA) ], null=False, blank=False, editable=True, unique=False, verbose_name='Availability', ) length = models.PositiveSmallIntegerField( null=False, blank=False, default=1, editable=True, unique=False, verbose_name='Presentation Length', ) visual_aids = models.CharField( max_length=300, null=True, blank=True, default='', editable=True, unique=False, verbose_name='Prepared Visual Aids', ) addl_visual_aids = models.CharField( max_length=300, null=True, blank=True, default='', editable=True, unique=False, verbose_name='Additional Visual Aids Required', ) addl_tech = models.CharField( max_length=300, null=True, blank=True, default='', editable=True, unique=False, verbose_name='Additional Tech Setup Required', ) consent_audio_rec = models.BooleanField( default=False, editable=True, verbose_name='Consent to Record Audio', ) consent_video_rec = models.BooleanField( default=False, editable=True, verbose_name='Consent to Record Video', ) consent_streaming = models.BooleanField( default=False, editable=True, verbose_name='Consent to Upload Recordings to Streaming Platform(s)', ) consent_materials = models.BooleanField( default=False, editable=True, verbose_name= 'Consent to Share Presentation Materials With Club Members', ) class Meta: """Defines the long-form name to label GuestSpeakerContactForm objects. """ verbose_name = 'Guest Speaker Contact Form'
class Event(models.Model): """A Django database model which represents a club event. Attributes: # noqa type: A CharField containing the type of event. The available types are defined in the EventType class. topics: A JSONField containing a JSON representation of a list of topics for an event, if any. start: A DateTimeField containing the date and time of the start of the event. end: A DateTimeField containing the date and time of the end of the event. calendar_link: An optional URLField containing a calendar invite link to add the event to your calendar. meeting_link: An optional URLField containing a virtual meeting link to join an online, virtual meeting. meeting_address: A many-to-one relation to the MeetingAddress model, which relates an Event to its location. contacts: A generic relation to the ContactInfo model in the core directory. objects: A custom Manager which includes all base Manager functionality with the addition of the `upcoming` method which can be used in place of `Event.objects.all()` to retrieve a QuerySet containing only upcoming event objects. """ class EventType(models.TextChoices): """Defines the supported types of events for the Event model's ``type`` field. Attributes: # noqa GUEST_SPEAKER: A 2 character identifier and lazily-evaluated label representing the choice of a presentation by a guest speaker. WORKSHOP: A 2 character identifier and lazily-evaluated label representing the choice of a workshop. INTERVIEW_PREP: A 2 character identifier and lazily-evaluated label representing the choice of an interview preparation session. DISCUSSION: A 2 character identifier and lazily-evaluated label representing the choice of a free form discussion. PROJECT_MEETING: A 2 character identifier and lazily-evaluated label representing the choice of a group project meeting. HACKATHON_MEETING: A 2 character identifier and lazily-evaluated label representing the choice of a hackathon meeting. OTHER: A 2 character identifier and lazily-evaluated label representing the catch-all choice for all other types of events. """ GUEST_SPEAKER = 'GS', _('Guest Speaker Presentation') WORKSHOP = 'WS', _('Workshop') INTERVIEW_PREP = 'IP', _('Interview Prep Session') DISCUSSION = 'DI', _('Free Form Discussion') PROJECT_MEETING = 'PM', _('Group Project Meeting') HACKATHON_MEETING = 'HM', _('Hackathon Meeting') OTHER = 'OT', _('Other Event') type = models.CharField( max_length=2, choices=EventType.choices, default=EventType.OTHER, null=False, blank=False, editable=True, unique=False, verbose_name='Type of Event', ) topics = models.JSONField( default=list, validators=[ JSONSchemaValidator(limit_value=EVENT_TOPICS_FIELD_JSON_SCHEMA) ], null=False, blank=True, editable=True, unique=False, verbose_name='Event Topics', ) start = models.DateTimeField( null=False, blank=False, editable=True, unique=False, verbose_name='Start Date and Time', ) end = models.DateTimeField( null=False, blank=False, editable=True, unique=False, verbose_name='End Date and Time', ) calendar_link = models.URLField( null=True, blank=True, editable=True, unique=False, verbose_name='Calendar Invite Link', max_length=400, ) meeting_link = models.URLField( null=True, blank=True, editable=True, unique=False, verbose_name='Virtual Meeting Link', ) meeting_address = models.ForeignKey( MeetingAddress, on_delete=models.SET_NULL, null=True, blank=True, default=None, editable=True, verbose_name='Meeting Location', ) contacts = GenericRelation('core.ContactInfo') objects = EventQuerySet.as_manager() __original_start = None def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__original_start = self.start def clean(self): """Provides additional validation for Event model fields. Ensures that an Event object's start datetime must fall before its end datetime. """ if self.start >= self.end: raise ValidationError( 'Event start date and time must fall before its end date and time.' ) def save(self, *args, **kwargs): """Overrides the default model save method to send a Celery task when a new Event object is saved, or an existing event is rescheduled. """ if self.pk and self.__original_start != self.start: super(Event, self).save(*args, **kwargs) event_rescheduled.delay( Event.EventType(self.type).label, self.start) else: super(Event, self).save(*args, **kwargs) event_created.delay(Event.EventType(self.type).label, self.start) def __str__(self): """Defines the string representation of the Event class. The string representation of an Event class instance contains the event type and the date(s) for which the event is scheduled. If the event occurs in a single day (i.e., its start and end dates are the same), only the start date is included. If the event occurs over the span of more than one day (i.e., its start and end dates are different), both the start and dates are included. Returns: A string containing the event's type and the date(s) when it is scheduled to occur. """ if self.start.date() == self.end.date(): return f'{self.EventType(self.type).label} on {self.start.strftime("%m-%d-%Y")}' else: return f'{self.EventType(self.type).label} from {self.start.strftime("%m-%d-%Y")} ' \ + f'to {self.end.strftime("%m-%d-%Y")}' class Meta: """This class contains meta-options for the Event model. Attributes: #noqa ordering: A list of fields to order Event objects by. As-is, they are ordered by the date/time they start and in ascending order (i.e., the Event that is starting the soonest appears first and the one starting latest appears last). """ ordering = ['start']
class Project(models.Model): """A Django database model which represents a group project. In the PostgreSQL database, a project has a name, a list of authors, a description, an image, a URL for an external website where the project (or its code) is hosted, a status (complete, in progress, etc.), and the date and time the project was last edited. Attributes: # noqa name: A CharField containing the name of the project. authors: A JSONField containing a JSON representation of a list of project authors as strings. description: A TextField containing a long-form description of the project. image: An ImageField representing the project's image in the database. url: An optional URLField containing a link to an external website where the project is hosted (e.g., github). status: A CharField containing the status of the project. The available statuses are defined in the ProjectStatus class. modified: A DateTimeField containing the date and time when the project was last edited. """ class ProjectStatus(models.TextChoices): """Defines the supported statuses of projects for the Project model's ``status`` field. Attributes: # noqa COMPLETE: A 2 character identifier and lazily-evaluated label representing the choice of the project being finished/finalized. IN_PROGRESS: A 2 character identifier and lazily-evaluated label representing the choice of the project being in progress and still being actively worked on. PLANNED: A 2 character identifier and lazily-evaluated label representing the choice of the project being planned for the future. OTHER: A 2 character identifier and lazily-evaluated label representing the catch-all choice for all other statuses of projects. """ COMPLETE = 'CO', _('Complete') IN_PROGRESS = 'IP', _('In Progress') PLANNED = 'PL', _('Planned') OTHER = 'OT', _('Other') name = models.CharField(max_length=250, null=False, blank=False, default='', editable=True, unique=True, verbose_name='Project Title') authors = models.JSONField( default=list, validators=[ JSONSchemaValidator(limit_value=PROJECT_AUTHORS_FIELD_JSON_SCHEMA) ], null=False, blank=False, editable=True, unique=False, verbose_name='Project Authors') description = models.TextField(null=False, blank=False, default='', editable=True, unique=False, verbose_name='Project Description') image = models.ImageField(null=True, blank=True, editable=True, unique=True, verbose_name='Project Image', upload_to=image_path) url = models.URLField(null=True, blank=True, editable=True, unique=True, verbose_name='External Project URL', max_length=200) status = models.CharField(max_length=2, choices=ProjectStatus.choices, default=ProjectStatus.PLANNED, null=False, blank=False, editable=True, unique=False, verbose_name='Project Status or Phase') modified = models.DateTimeField(auto_now=True, null=False, blank=True, editable=False, unique=False, verbose_name='Date and Time of Last Edit') def save(self, *args, **kwargs): """Overrides the default model save method to send a Celery task when a new Project object is created/saved. """ if not self.pk: project_created.delay(self.name, self.authors, self.description, self.url) super(Project, self).save(*args, **kwargs) def __str__(self): """Defines the string representation of the Project model. The string representation of a Project object only contains the project's name. Returns: A string containing the project's name. """ return self.name