예제 #1
0
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
예제 #2
0
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
예제 #3
0
class User(AbstractBaseUser, PermissionsMixin):

    email = models.EmailField(
        verbose_name=_('email address'),
        max_length=255, unique=True, db_index=True,
    )
    speaker_name = models.CharField(
        verbose_name=_('speaker name'),
        max_length=100,
    )
    bio = EAWTextField(
        verbose_name=_('biography'),
        max_length=1000,
        help_text=_(
            "Describe yourself with 1000 characters or less. "
            "There will be no formatting."
        ),
    )
    photo = models.ImageField(
        verbose_name=_('photo'),
        blank=True, default='', upload_to=photo_upload_to,
    )
    facebook_profile_url = models.URLField(
        verbose_name=_('Facebook'),
        blank=True,
        help_text=format_html_lazy(_(
            "Link to your Facebook profile page. This will be shown when "
            "we display your public information. If you do not know what your "
            "profile page link is, click on "
            "<a href='https://www.facebook.com/me' target='_blank'>"
            "this link</a>, and copy-paste the URL of the page opened. "
            "Remember to log in to Facebook first!"
        )),
    )
    twitter_id = models.CharField(
        verbose_name=_('Twitter'),
        blank=True, max_length=100, validators=[
            RegexValidator(r'^[0-9a-zA-Z_]*$', 'Not a valid Twitter handle'),
        ],
        help_text=_(
            "Your Twitter handle, without the \"@\" sign. This will be "
            "shown when we display your public information."
        ),
    )
    github_id = models.CharField(
        verbose_name=_('GitHub'),
        blank=True, max_length=100, validators=[
            RegexValidator(r'^[0-9a-zA-Z_-]*$', 'Not a valid GitHub account'),
        ],
        help_text=_(
            "Your GitHub account, without the \"@\" sign. This will be "
            "shown when we display your public information."
        ),
    )
    verified = models.BooleanField(
        verbose_name=_('verified'),
        default=False,
        help_text=_(
            "Designates whether the user has verified email ownership."
        ),
    )
    is_staff = models.BooleanField(
        verbose_name=_('staff status'),
        default=False,
        help_text=_(
            "Designates whether the user can log into this admin site."
        ),
    )
    is_active = models.BooleanField(
        verbose_name=_('active'),
        default=True,
        help_text=_(
            "Designates whether this user should be treated as "
            "active. Unselect this instead of deleting accounts."
        ),
    )
    date_joined = models.DateTimeField(
        verbose_name=_('date joined'),
        default=timezone.now,
    )

    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')
        swappable = 'AUTH_USER_MODEL'

    def __str__(self):
        return self.email

    def as_hash(self):
        """Return the user's hash representation.

        Pipeline:
            - pk -> md5 -> first 2 bytes -> b16 -> str.
        """
        return '#{hash_user}'.format(
            hash_user=(
                base64.b16encode(
                    # Picking the first 2 bytes may still run into hash
                    # collisions if you take the whole users list into account,
                    # but as this is used for representing reviewers which is a
                    # comparatively small subset, it's not a big deal.
                    hashlib.md5(str(self.pk).encode('utf-8')).digest()[:2],
                ).decode('utf-8')
            )
        )

    as_hash.short_description = _('Reviewer ID')

    def get_full_name(self):
        return self.speaker_name

    def get_short_name(self):
        return self.speaker_name

    def get_thumbnail_url(self):
        """Return URL to compressed profile photo if set, or a default image.
        """
        if self.photo and os.path.exists(self.photo.path):
            return get_thumbnail(self.photo, '800x800').url
        return static('images/default_head.png')

    def is_valid_speaker(self):
        """Whether the user is a valid speaker.

        :seealso: ``UserQuerySet.get_valid_speakers``
        """
        return (
            self.verified and self.is_active and
            self.speaker_name and self.bio
        )

    def is_reviewer(self):
        return self.has_perm('reviews.add_review')

    @property
    def cospeaking_info_set(self):
        return self.additionalspeaker_set.filter(
            cancelled=False,
            conference=settings.CONFERENCE_DEFAULT_SLUG,
        )

    @property
    def twitter_profile_url(self):
        if not self.twitter_id:
            return ""
        return 'https://twitter.com/{}'.format(self.twitter_id)

    @property
    def github_profile_url(self):
        if not self.github_id:
            return ""
        return 'https://github.com/{}'.format(self.github_id)

    def get_verification_key(self):
        key = signing.dumps(
            obj=getattr(self, self.USERNAME_FIELD),
            salt=settings.SECRET_KEY,
        )
        return key

    def email_user(self, subject, message, from_email=None, **kwargs):
        """Send an email to this user.
        """
        send_mail(subject, message, from_email, [self.email], **kwargs)

    def send_verification_email(self, request):
        verification_key = self.get_verification_key()
        verification_url = request.build_absolute_uri(
            reverse('user_verify', kwargs={
                'verification_key': verification_key,
            }),
        )
        context = {
            'user': self,
            'host': request.get_host(),
            'verification_key': verification_key,
            'verification_url': verification_url,
        }
        message = render_to_string(
            'registration/verification_email.txt', context,
        )
        self.email_user(
            subject=gettext('Verify your email address on {host}').format(
                **context
            ),
            message=message, fail_silently=False,
        )

    @property
    def has_agreed_coc(self):
        return CocRecord.objects.filter(user=self, coc_version=settings.COC_VERSION).count() == 1
예제 #4
0
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
예제 #5
0
class User(AbstractBaseUser, PermissionsMixin):

    email = models.EmailField(
        verbose_name=_('email address'),
        max_length=255, unique=True, db_index=True,
    )
    speaker_name = models.CharField(
        verbose_name=_('speaker name'),
        max_length=100,
    )
    bio = EAWTextField(
        verbose_name=_('biography'),
        max_length=1000,
        help_text=_(
            "Describe yourself with 500 characters or less. "
            "There will be no formatting."
        ),
    )
    photo = models.ImageField(
        verbose_name=_('photo'),
        blank=True, default='', upload_to=photo_upload_to,
    )
    facebook_profile_url = models.URLField(
        verbose_name=_('Facebook'),
        blank=True,
        help_text=format_html_lazy(_(
            "Link to your Facebook profile page. This will be shown when "
            "we display your public information. If you do not know what your "
            "profile page link is, click on "
            "<a href='https://www.facebook.com/me' target='_blank'>"
            "this link</a>, and copy-paste the URL of the page opened. "
            "Remember to log in to Facebook first!"
        )),
    )
    twitter_id = models.CharField(
        verbose_name=_('Twitter'),
        blank=True, max_length=100, validators=[
            RegexValidator(r'^[0-9a-zA-Z_]*$', 'Not a valid Twitter handle'),
        ],
        help_text=_(
            "Your Twitter handle, without the \"@\" sign. This will be "
            "shown when we display your public information."
        ),
    )
    github_id = models.CharField(
        verbose_name=_('GitHub'),
        blank=True, max_length=100, validators=[
            RegexValidator(r'^[0-9a-zA-Z_-]*$', 'Not a valid GitHub account'),
        ],
        help_text=_(
            "Your GitHub account, without the \"@\" sign. This will be "
            "shown when we display your public information."
        ),
    )
    verified = models.BooleanField(
        verbose_name=_('verified'),
        default=False,
        help_text=_(
            "Designates whether the user has verified email ownership."
        ),
    )
    is_staff = models.BooleanField(
        verbose_name=_('staff status'),
        default=False,
        help_text=_(
            "Designates whether the user can log into this admin site."
        ),
    )
    is_active = models.BooleanField(
        verbose_name=_('active'),
        default=True,
        help_text=_(
            "Designates whether this user should be treated as "
            "active. Unselect this instead of deleting accounts."
        ),
    )
    date_joined = models.DateTimeField(
        verbose_name=_('date joined'),
        default=timezone.now,
    )

    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')
        swappable = 'AUTH_USER_MODEL'

    def __str__(self):
        return self.email

    def get_full_name(self):
        return self.speaker_name

    def get_short_name(self):
        return self.speaker_name

    def is_valid_speaker(self):
        """Whether the user is a valid speaker.

        :seealso: ``UserQuerySet.get_valid_speakers``
        """
        return (
            self.verified and self.is_active and
            self.speaker_name and self.bio
        )

    def is_reviewer(self):
        return self.has_perm('reviews.add_review')

    @property
    def cospeaking_info_set(self):
        return self.additionalspeaker_set.filter(
            cancelled=False,
            conference=settings.CONFERENCE_DEFAULT_SLUG,
        )

    @property
    def twitter_profile_url(self):
        return 'https://twitter.com/{}'.format(self.twitter_id)

    @property
    def github_profile_url(self):
        return 'https://github.com/{}'.format(self.github_id)

    def get_verification_key(self):
        key = signing.dumps(
            obj=getattr(self, self.USERNAME_FIELD),
            salt=settings.SECRET_KEY,
        )
        return key

    def email_user(self, subject, message, from_email=None, **kwargs):
        """Send an email to this user.
        """
        send_mail(subject, message, from_email, [self.email], **kwargs)

    def send_verification_email(self, request):
        verification_key = self.get_verification_key()
        verification_url = request.build_absolute_uri(
            reverse('user_verify', kwargs={
                'verification_key': verification_key,
            }),
        )
        context = {
            'user': self,
            'host': request.get_host(),
            'verification_key': verification_key,
            'verification_url': verification_url,
        }
        message = render_to_string(
            'registration/verification_email.txt', context,
        )
        self.email_user(
            subject=ugettext('Verify your email address on {host}').format(
                **context
            ),
            message=message, fail_silently=False,
        )