class CommunityTrackEvent(models.Model): venue = models.ForeignKey(Venue, on_delete=models.CASCADE) talk = BigForeignKey(TalkProposal, on_delete=models.CASCADE, null=True, blank=True) sponsored_event = BigForeignKey(SponsoredEvent, on_delete=models.CASCADE, null=True, blank=True) order = models.IntegerField(default=0) class Meta: verbose_name = _('community track event') verbose_name_plural = _('community track events') ordering = ['order'] def __str__(self): return "%d. %s (%s)" % (self.order, self.get_event(), self.venue.name) def clean(self): if self.talk and self.sponsored_event: raise ValidationError( _('You can only put either proposed_talk_event or sponsored_event at once.' )) if not self.talk and not self.sponsored_event: raise ValidationError( _('You need to put proposed_talk_event or sponsored_event.')) def get_event(self): return self.talk or self.sponsored_event
class CommunityTrackEvent(models.Model): venue = models.ForeignKey(Venue, on_delete=models.CASCADE, null=True, blank=True) talk = BigForeignKey(TalkProposal, on_delete=models.CASCADE, null=True, blank=True) sponsored_event = BigForeignKey(SponsoredEvent, on_delete=models.CASCADE, null=True, blank=True) custom_event = models.CharField(max_length=140, blank=True) order = models.IntegerField(default=0) begin_time = models.ForeignKey( to=Time, blank=True, null=True, related_name='beginning_%(class)s_set', verbose_name=_('begin time'), on_delete=models.CASCADE, ) end_time = models.ForeignKey( to=Time, blank=True, null=True, related_name='ending_%(class)s_set', verbose_name=_('end time'), on_delete=models.CASCADE, ) class Meta: verbose_name = _('community track event') verbose_name_plural = _('community track events') ordering = ['begin_time', 'order'] def __str__(self): return "%d. %s" % (self.order, self.get_event()) def clean(self): values = [self.talk, self.sponsored_event, self.custom_event] count = len(tuple(filter(None, values))) if count > 1: raise ValidationError( _('You can only put either proposed_talk_event, sponsored_event or custom_event at once.' )) if count <= 0: raise ValidationError( _('You need to put proposed_talk_event sponsored_event, or custom_event.' )) def get_event(self): return self.talk or self.sponsored_event or self.custom_event
class ProposedTutorialEvent(BaseEvent): proposal = BigForeignKey( to=TutorialProposal, verbose_name=_('proposal'), ) registration_link = models.URLField( verbose_name=_('registration link'), blank=True, default='', ) objects = ProposedEventManager() class Meta: verbose_name = _('tutorial event') verbose_name_plural = _('tutorial events') def __str__(self): return self.proposal.title def get_absolute_url(self): return reverse('events_tutorial_detail', kwargs={ 'pk': self.proposal.pk, })
class AbstractProposal(EventInfo): submitter = BigForeignKey( to=settings.AUTH_USER_MODEL, verbose_name=_('submitter'), ) objective = EAWTextField( verbose_name=_('objective'), max_length=1000, help_text=_( "Who is the intended audience for your talk? (Be specific, " "\"Python users\" is not a good answer). " "And what will the attendees get out of your talk? When they " "leave the room, what will they learn that they didn't know " "before? This is NOT made public and for REVIEW ONLY." ), ) supplementary = models.TextField( verbose_name=_('supplementary'), blank=True, default='', help_text=_( "Anything else you'd like the program committee to know when " "making their selection: your past speaking experience, community " "experience, etc. This is NOT made public and for REVIEW ONLY. " "Edit using " "<a href='http://daringfireball.net/projects/markdown/basics' " "target='_blank'>Markdown</a>." ), ) cancelled = models.BooleanField( verbose_name=_('cancelled'), default=False, db_index=True, ) additionalspeaker_set = GenericRelation( to=AdditionalSpeaker, content_type_field='proposal_type', object_id_field='proposal_id', ) objects = ProposalQuerySet.as_manager() class Meta: abstract = True @property def speakers(self): yield PrimarySpeaker(self) additionals = self.additionalspeaker_set.filter(cancelled=False) for speaker in additionals.select_related('user'): yield speaker @property def speaker_count(self): return self.additionalspeaker_set.filter(cancelled=False).count() + 1
class ProposedTalkEvent(BaseEvent): proposal = BigForeignKey( to=TalkProposal, limit_choices_to={'accepted': True}, verbose_name=_('proposal'), on_delete=models.CASCADE, unique=True, ) is_remote = models.BooleanField( verbose_name=_('is remote'), default=False, ) objects = ProposedEventManager() class Meta: verbose_name = _('talk event') verbose_name_plural = _('talk events') def __str__(self): return self.proposal.title def get_absolute_url(self): return reverse('events_talk_detail', kwargs={'pk': self.proposal.pk})
class TalkProposalSnapshot(models.Model): """Snapshot for a talk proposal during a review stage. """ proposal = BigForeignKey( to=TalkProposal, verbose_name=_('proposal'), ) stage = models.IntegerField(verbose_name=_('stage'), ) dumped_json = models.TextField(verbose_name=_('dumped JSON'), ) dumped_at = models.DateTimeField( verbose_name=_('dumped at'), auto_now=True, ) class Meta: verbose_name = _('talk proposal snapshot') verbose_name_plural = _('talk proposal snapshots') unique_together = ['proposal', 'stage'] def __str__(self): return ugettext('Stage {stage} dump for {proposal}').format( stage=self.stage, proposal=self.proposal, )
class OpenRole(models.Model): sponsor = BigForeignKey( to=Sponsor, verbose_name=_("sponsor"), on_delete=models.CASCADE, ) name = models.CharField( verbose_name=_('open role name'), max_length=100, ) description = models.TextField(verbose_name=_('open role descsription'), ) url = models.URLField( verbose_name=_('open role URL'), max_length=255, blank=True, ) class Meta: verbose_name = _('open role') verbose_name_plural = _('open Roles') def __str__(self): return self.name
class SponsoredEvent(EventInfo, BaseEvent): host = BigForeignKey( to=settings.AUTH_USER_MODEL, verbose_name=_('host'), on_delete=models.CASCADE, ) slug = models.SlugField( allow_unicode=True, verbose_name=_('slug'), ) class Meta: verbose_name = _('sponsored event') verbose_name_plural = _('sponsored events') def get_absolute_url(self): return reverse('events_sponsored_event_detail', kwargs={ 'slug': self.slug, }) @property def speakers(self): yield PrimarySpeaker(user=self.host)
class CommunityTrackEvent(models.Model): venue = models.ForeignKey(Venue, on_delete=models.CASCADE) talk = BigForeignKey(TalkProposal, on_delete=models.CASCADE) order = models.IntegerField(default=0) def __str__(self): return "%d. %s (%s)" % (self.order, self.talk.title, self.venue.name)
class CocRecord(models.Model): user = BigForeignKey( to=settings.AUTH_USER_MODEL, verbose_name=_('user'), on_delete=models.CASCADE, ) coc_version = models.CharField(verbose_name=_('latest agreed CoC version'), max_length=15) agreed_at = models.DateTimeField( verbose_name=_('agreed at'), auto_now_add=True, blank=True, null=True, )
class AdditionalSpeaker(ConferenceRelated): user = BigForeignKey( to=settings.AUTH_USER_MODEL, verbose_name=_('user'), on_delete=models.CASCADE, ) proposal_type = models.ForeignKey( to='contenttypes.ContentType', verbose_name=_('proposal model type'), on_delete=models.CASCADE, ) proposal_id = models.BigIntegerField( verbose_name=_('proposal ID'), ) proposal = GenericForeignKey('proposal_type', 'proposal_id') SPEAKING_STATUS_PENDING = 'pending' SPEAKING_STATUS_ACCEPTED = 'accepted' SPEAKING_STATUS_DECLINED = 'declined' SPEAKING_STATUS = ( (SPEAKING_STATUS_PENDING, _('Pending')), (SPEAKING_STATUS_ACCEPTED, _('Accepted')), (SPEAKING_STATUS_DECLINED, _('Declined')), ) status = models.CharField( max_length=8, choices=SPEAKING_STATUS, default=SPEAKING_STATUS_PENDING, ) cancelled = models.BooleanField( verbose_name=_('cancelled'), default=False, db_index=True, ) class Meta: unique_together = ['user', 'proposal_type', 'proposal_id'] ordering = ['proposal_type', 'proposal_id', 'pk'] verbose_name = _('additional speaker') verbose_name_plural = _('additional speakers') def __str__(self): return '{name} ({status})'.format( name=self.user.speaker_name, status=self.get_status_display(), )
class JobListingsEvent(BaseEvent): sponsor = BigForeignKey( to=Sponsor, verbose_name=_("sponsor"), on_delete=models.CASCADE, ) class Meta: verbose_name = _('Job Listings') verbose_name_plural = _('Job Listings') def __str__(self): return gettext('Open Role of Sponsor: {sponsor}'.format( sponsor=self.sponsor, ))
class ProposedTalkEvent(BaseEvent): proposal = BigForeignKey( to=TalkProposal, limit_choices_to={'accepted': True}, verbose_name=_('proposal'), ) objects = ProposedEventManager() class Meta: verbose_name = _('talk event') verbose_name_plural = _('talk events') def __str__(self): return self.proposal.title def get_absolute_url(self): return reverse('events_talk_detail', kwargs={'pk': self.proposal.pk})
class Migration(migrations.Migration): dependencies = [ ('proposals', '0036_add_proposal_update_timestamp'), ('reviews', '0015_review_appropriateness'), ] operations = [ migrations.CreateModel( name='TalkProposalSnapshot', fields=[ ('id', models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name='ID', )), ('stage', models.IntegerField(verbose_name='stage', )), ('dumped_json', models.TextField(verbose_name='dumped JSON', )), ('dumped_at', models.DateTimeField( auto_now=True, verbose_name='dumped at', )), ('proposal', BigForeignKey( on_delete=models.deletion.CASCADE, to='proposals.TalkProposal', verbose_name='proposal', )), ], options={ 'verbose_name': 'talk proposal snapshot', 'verbose_name_plural': 'talk proposal snapshots', }, ), migrations.AlterUniqueTogether( name='talkproposalsnapshot', unique_together={('proposal', 'stage')}, ), ]
class SponsoredEvent(EventInfo): host = BigForeignKey( to=settings.AUTH_USER_MODEL, verbose_name=_('host'), ) slug = models.SlugField( allow_unicode=True, verbose_name=_('slug'), ) class Meta: verbose_name = _('sponsored event') verbose_name_plural = _('sponsored events') def get_absolute_url(self): return reverse('events_sponsored_event_detail', kwargs={ 'slug': self.slug, })
class ProposedTutorialEvent(BaseEvent): proposal = BigForeignKey( to=TutorialProposal, verbose_name=_('proposal'), on_delete=models.CASCADE, unique=True, ) registration_link = models.URLField( verbose_name=_('registration link'), blank=True, default='', ) is_remote = models.BooleanField( verbose_name=_('is remote'), default=False, ) youtube_id = models.CharField( verbose_name=_('youtube id'), max_length=20, blank=True, ) objects = ProposedEventManager() class Meta: verbose_name = _('tutorial event') verbose_name_plural = _('tutorial events') def __str__(self): return self.proposal.title def get_absolute_url(self): return reverse('events_tutorial_detail', kwargs={ 'pk': self.proposal.pk, })
class Review(models.Model): reviewer = BigForeignKey( to=settings.AUTH_USER_MODEL, verbose_name=_('reviewer'), ) stage = models.IntegerField(verbose_name=_('stage'), ) proposal = models.ForeignKey( to=TalkProposal, verbose_name=_('proposal'), ) objects = ReviewManager() all_objects = ReviewQuerySet.as_manager() class Vote: PLUS_ONE = '+1' PLUS_ZERO = '+0' MINUS_ZERO = '-0' MINUS_ONE = '-1' VOTE_CHOICES = ( (Vote.PLUS_ONE, _('+1 (strong accept)')), (Vote.PLUS_ZERO, _('+0 (weak accept)')), (Vote.MINUS_ZERO, _('-0 (weak reject)')), (Vote.MINUS_ONE, _('-1 (strong reject)')), ) VOTE_ORDER = {vote: i for i, (vote, _) in enumerate(VOTE_CHOICES)} vote = models.CharField( max_length=2, blank=False, choices=VOTE_CHOICES, verbose_name=_("vote"), help_text=_( "Your vote to accept or reject this talk. " "More information about the scoring and acceptance criteria " "can be found at the GitBook " "<a href=\"https://pycontw.github.io/reviewer-guidebook\" " "target=\"_blank\">Review Guideline</a>."), ) comment = models.TextField( verbose_name=_('comment'), help_text=_( "Comments to this proposal. This may be available for other " "reviewers in later review stages, and you can choose whether " "or not to disclose it to the proposal's submitter."), ) DISCLOSE_CHOICES = ( (True, _('Yes')), (False, _('No')), ) discloses_comment = models.BooleanField( default=True, choices=DISCLOSE_CHOICES, verbose_name=_('discloses comment to proposal submitter'), help_text=_( "Whether the proposal submitter can read you comments. We will " "include your comments in the proposal acceptance/rejection " "notice sent to the submitter if you allow us to.")) APPROPRIATENESS_CHOICES = ( (True, _('Yes')), (False, _('No')), ) appropriateness = models.BooleanField( default=False, choices=APPROPRIATENESS_CHOICES, verbose_name=_('is appropriate'), help_text=_( "Administrators can use this field to hide a review from " "submitters, even if the reviewer enables disclosure. The review " "may be shown to the submitter only if this is set to True."), ) note = models.TextField( blank=True, verbose_name=_('note'), help_text=_( "Personal notes about this proposal. You can use this field to " "record anything you like during the review process. We promise " "to never disclose them to anyone."), ) updated = models.DateTimeField( auto_now=True, verbose_name=_('updated'), db_index=True, ) class Meta: verbose_name = _('review') verbose_name_plural = _('reviews') unique_together = ['reviewer', 'stage', 'proposal'] ordering = ['-updated'] def __str__(self): return ugettext('Review {proposal} by {reviewer}: {vote}').format( reviewer=self.reviewer, proposal=self.proposal, vote=self.get_vote_display(), ) def save(self, *args, **kwargs): if self.stage is None: self.stage = settings.REVIEWS_STAGE return super().save(*args, **kwargs) def is_comment_visible_to_submitter(self): return self.discloses_comment and self.appropriateness def is_outdated(self): return self.updated < self.proposal.last_updated_at
class AbstractProposal(ConferenceRelated, EventInfo): submitter = BigForeignKey( to=settings.AUTH_USER_MODEL, verbose_name=_('submitter'), on_delete=models.CASCADE, ) outline = models.TextField( verbose_name=_('outline'), blank=True, ) objective = EAWTextField( verbose_name=_('objective'), max_length=1000, ) supplementary = models.TextField( verbose_name=_('supplementary'), blank=True, default='', ) cancelled = models.BooleanField( verbose_name=_('cancelled'), default=False, db_index=True, ) ACCEPTED_CHOICES = ( (None, '----------'), (True, _('Accepted')), (False, _('Rejected')), ) accepted = models.NullBooleanField( verbose_name=_('accepted'), default=None, choices=ACCEPTED_CHOICES, db_index=True, ) additionalspeaker_set = GenericRelation( to=AdditionalSpeaker, content_type_field='proposal_type', object_id_field='proposal_id', ) objects = DefaultConferenceManager.from_queryset(ProposalQuerySet)() all_objects = ProposalQuerySet.as_manager() _must_fill_fields = [ 'abstract', 'objective', 'supplementary', 'detailed_description', 'outline', ] class Meta: abstract = True @property def speakers(self): yield PrimarySpeaker(proposal=self) # Optimization: Callers of this method can annotate the queryset to # avoid lookups when a proposal doesn't have any additional speakers. with contextlib.suppress(AttributeError): if self._additional_speaker_count < 1: return # Optimization: Callers of this method can prefetch the additional # speaker queryset to avoid n+1 lookups when operating on multiple # proposals. Example:: # # proposals = TalkProposal.objects.prefetch_related(Prefetch( # 'additionalspeaker_set', # to_attr='_additional_speakers', # queryset=( # AdditionalSpeaker.objects # .filter(cancelled=False) # .select_related('user') # ), # )) # for p in proposals: # Only two queries: proposals, and speakers. # for s in p.speakers: # print(s.user.email) try: additionals = self._additional_speakers except AttributeError: additionals = ( self.additionalspeaker_set .filter(cancelled=False) .select_related('user') ) for speaker in additionals: yield speaker @property def speaker_count(self): # Optimization: Callers of this method can annotate the queryset to # avoid n+1 lookups when operating on multiple proposals. try: count = self._additional_speaker_count except AttributeError: count = self.additionalspeaker_set.filter(cancelled=False).count() return count + 1 @property def must_fill_fields_count(self): return len(self._must_fill_fields) @property def finished_fields_count(self): count = sum(1 for f in self._must_fill_fields if getattr(self, f)) return count @property def finish_percentage(self): return 100 * self.finished_fields_count // self.must_fill_fields_count @property def unfinished_fields_count(self): return self.must_fill_fields_count - self.finished_fields_count
class AbstractProposal(ConferenceRelated, EventInfo): submitter = BigForeignKey( to=settings.AUTH_USER_MODEL, verbose_name=_('submitter'), ) outline = models.TextField( verbose_name=_('outline'), blank=True, ) objective = EAWTextField( verbose_name=_('objective'), max_length=1000, ) supplementary = models.TextField( verbose_name=_('supplementary'), blank=True, default='', ) cancelled = models.BooleanField( verbose_name=_('cancelled'), default=False, db_index=True, ) additionalspeaker_set = GenericRelation( to=AdditionalSpeaker, content_type_field='proposal_type', object_id_field='proposal_id', ) objects = DefaultConferenceManager.from_queryset(ProposalQuerySet)() all_objects = ProposalQuerySet.as_manager() _must_fill_fields = [ 'abstract', 'objective', 'supplementary', 'detailed_description', 'outline', ] class Meta: abstract = True @property def speakers(self): yield PrimarySpeaker(proposal=self) additionals = self.additionalspeaker_set.filter(cancelled=False) for speaker in additionals.select_related('user'): yield speaker @property def speaker_count(self): return self.additionalspeaker_set.filter(cancelled=False).count() + 1 @property def must_fill_fields_count(self): return len(self._must_fill_fields) @property def finished_fields_count(self): count = sum(1 for f in self._must_fill_fields if getattr(self, f)) return count @property def finish_percentage(self): return 100 * self.finished_fields_count // self.must_fill_fields_count @property def unfinished_fields_count(self): return self.must_fill_fields_count - self.finished_fields_count
class Review(models.Model): reviewer = BigForeignKey( to=settings.AUTH_USER_MODEL, verbose_name=_('reviewer'), ) stage = models.IntegerField( verbose_name=_('stage'), ) proposal = models.ForeignKey( to=TalkProposal, verbose_name=_('proposal'), ) class Vote(object): PLUS_ONE = "+1" PLUS_ZERO = "+0" MINUS_ZERO = "-0" MINUS_ONE = "-1" VOTE_CHOICES = ( (Vote.PLUS_ONE, _('+1 (strong accept)')), (Vote.PLUS_ZERO, _('+0 (weakly accept)')), (Vote.MINUS_ZERO, _('-0 (weakly reject)')), (Vote.MINUS_ONE, _('-1 (strong reject)')), ) vote = models.CharField( max_length=2, blank=True, choices=VOTE_CHOICES, verbose_name=_("vote"), help_text=_( "Your vote to accept or reject this talk. " "More information about the scoring and acceptance criteria " "can be found at the google doc " "<a href=\"https://goo.gl/EPlUZx\" " "target=\"_blank\">Review Guideline</a>." ), ) comment = models.TextField( verbose_name=_('comment'), help_text=_( "Comments to this proposal. This may be available for other " "reviewers in later review stages, and you can choose whether " "or not to disclose it to the proposal's submitter." ), ) DISCLOSE_CHOICES = ( (True, _('Yes')), (False, _('No')), ) discloses_comment = models.BooleanField( default=True, choices=DISCLOSE_CHOICES, verbose_name=_('discloses comment to proposal submitter'), help_text=_( "Whether the proposal submitter can read you comments. We will " "include your comments in the proposal acceptance/rejection " "notice sent to the submitter if you allow us to." ) ) note = models.TextField( blank=True, verbose_name=_('note'), help_text=_( "Personal notes about this proposal. You can use this field to " "record anything you like during the review process. We promise " "to never disclose them to anyone." ), ) updated = models.DateTimeField( auto_now=True, verbose_name=_('updated'), db_index=True, ) class Meta: verbose_name = _('review') verbose_name_plural = _('reviews') unique_together = ['reviewer', 'stage', 'proposal'] ordering = ['-updated'] def __str__(self): return _('Review {proposal} by {reviewer}: {vote}').format( reviewer=self.reviewer, proposal=self.proposal, vote=self.get_vote_display(), ) def save(self, *args, **kwargs): if self.stage is None: self.stage = ReviewsConfig.stage return super().save(*args, **kwargs)