class KeynoteEvent(BaseEvent): speaker_name = models.CharField( verbose_name=_('speaker name'), max_length=100, ) slug = models.SlugField( verbose_name=_('slug'), help_text=format_html_lazy( _("This is used to link to the speaker's introduction on the " "Keynote page, e.g. 'liang2' will link to " "'{link}#keynote-speaker-liang2'."), link=reverse_lazy('page', kwargs={'path': 'events/keynotes'}), )) class Meta: verbose_name = _('keynote event') verbose_name_plural = _('keynote events') def __str__(self): return ugettext('Keynote: {speaker}'.format( speaker=self.speaker_name, )) def get_absolute_url(self): url = reverse('page', kwargs={'path': 'events/keynotes'}) split = urllib.parse.urlsplit(url) frag = 'keynote-speaker-{slug}'.format(slug=self.slug) return urllib.parse.urlunsplit(split._replace(fragment=frag))
class KeynoteEvent(BaseEvent): speaker_name = models.CharField( verbose_name=_('speaker name'), max_length=100, ) slug = models.SlugField( verbose_name=_('slug'), help_text=format_html_lazy( _("This is used to link to the speaker's introduction on the " "Keynote page, e.g. 'liang2' will link to " "'{link}#keynote-speaker-liang2'."), link=reverse_lazy('page', kwargs={'path': 'conference/keynotes'}), )) is_remote = models.BooleanField( verbose_name=_('is remote'), default=False, ) class Meta: verbose_name = _('keynote event') verbose_name_plural = _('keynote events') def __str__(self): return gettext('Keynote: {speaker}'.format( speaker=self.speaker_name, )) def get_absolute_url(self): url = reverse('page', kwargs={'path': 'conference/keynotes'}) split = urllib.parse.urlsplit(url) frag = 'keynote-speaker-{slug}'.format(slug=self.slug) return urllib.parse.urlunsplit(split._replace(fragment=frag)) def get_static_data(self): path = '/'.join([ settings.CONFERENCE_DEFAULT_SLUG, 'assets/keynotes', f'{self.slug}.json', ]) keynote_info = finders.find(path) if not keynote_info: raise FileNotFoundError(path) with open(keynote_info) as f: data = json.load(f) return data def get_static_data_for_locale(self, code=None): if code is None: code = get_language() code = code.split('-', 1)[0] data = self.get_static_data() data = { k: v[code] if isinstance(v, dict) and code in v else v for k, v in data.items() } return data
class Meta: model = TalkProposal widgets = { 'abstract': CharacterCountedTextarea(), 'objective': CharacterCountedTextarea(), 'detailed_description': SimpleMDEWidget(), 'outline': SimpleMDEWidget(), 'supplementary': SimpleMDEWidget(), 'pre_recorded_policy': forms.CheckboxInput(), } help_texts = { 'abstract': _( "<p><a href='#' data-toggle='modal' " "data-target='#proposalFieldExampleModal' " "data-content='abstract'>Proposal Examples</a>.</p>" "<p>The overview of what the talk is about. If the talk " "assume some domain knowledge please state it here. If your " "talk is accepted, this will be displayed on both the website " "and the handbook. Should be one paragraph.</p>" ), 'python_level': format_html_lazy( _("The choice of talk level matters during the review " "process. More definition of talk level can be found at the " "<a href='{frontend_host}/en-us/speaking/talk' target='_blank'>" "How to Propose a Talk</a> page. Note that a proposal won't " "be more likely to be accepted because of being 'Novice' " "level. We may contact you to change the talk level when " "we find the content is too-hard or too-easy for the " "target audience."), frontend_host=f"{settings.FRONTEND_HOST}", ), 'detailed_description': _( "<p><a href='#' data-toggle='modal' " "data-target='#proposalFieldExampleModal' " "data-content='detailed description'>" "Proposal Examples</a>.</p>" "<p>Try not be too lengthy to scare away reviewers or " "potential audience. A comfortable length is less than 2000 " "characters (or about 1200 Chinese characters). Since most " "reviewers may not understand the topic as deep as you do, " "including related links to the talk topic will help " "reviewers understand the proposal. Edit using " "<a href='http://daringfireball.net/projects/markdown/basics' " "target='_blank' rel='noopener'>Markdown</a>." ), 'recording_policy': format_html_lazy( _("PyCon Taiwan will do post-processing to all the presentation " "videos, including but not limited to applying video template, " "banners of sponsors’ logos. Do you agree to give permission to " "PyCon Taiwan to release audio and video of your presentation " "after the conference? More information can be found at " "<a href='{frontend_host}/en-us/speaking/recording' target='_blank'>" "Recording Release</a> page."), frontend_host=f"{settings.FRONTEND_HOST}" ), 'slide_link': _( "You can add your slide link near or after the conference " "day. Not required for review." ), 'referring_policy': _( "Whether you agree to give permission to PyCon Taiwan to refer your " "talk proposal to your local PyCon community if your proposal did not " "get accepted(APAC only)." ), 'objective': _( "<p><a href='#' data-toggle='modal' " "data-target='#proposalFieldExampleModal' " "data-content='objective'>Proposal Examples</a>.</p>" "<p>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.</p>" ), 'supplementary': _( "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' rel='noopener'>Markdown</a>." ), 'outline': _( "<p><a href='#' data-toggle='modal' " "data-target='#proposalFieldExampleModal' " "data-content='outline'>Proposal Examples</a>.</p>" "<p>How the talk will be arranged. It is highly recommended " "to attach the estimated time length for each sections in the " "talk. Talks in favor of 45min should have a fallback plan " "about how to shrink the content into a 30min one. Edit using " "<a href='http://daringfireball.net/projects/markdown/basics' " "target='_blank' rel='noopener'>Markdown</a>. " "This is NOT made public and for REVIEW ONLY.</p>" ), 'live_stream_policy': _( "If you disagree to authorize PyCon Taiwan to record video, " "whether you agree to let PyCon Taiwan live stream your presentation " "only for the remote attendees to watch ? (we will not release " "audio and video of your presentation)" ), 'first_time_speaker': _( "Have you ever been a speaker at PyCon Taiwan in the past?" ), 'pre_recorded_policy': _( "Due to the ongoing global pandemic, <strong>PyCon APAC 2022 will " "be held fully remote. All online talks this year will be pre-recorded " "</strong> so we can broadcast your presentation to the audience " "without the network troubles and the time zone differences.<br>" "After your proposal is accepted, a pre-recording of the presentation " "will be provided to the PyCon APAC 2022 organizing team prior to the " "conference.<br>" "<strong>Bonus for living in Taiwan</strong><br>" "For speakers who reside in Taiwan, we will offer a slot and assist you " "to record the presentation in a professional video studio in Taipei " "(travel expenses covered)." ), 'talk_language': _( "If your previous option is not English, please enter your speaking language" ), }
class Meta: model = TalkProposal widgets = { 'abstract': CharacterCountedTextarea(), 'objective': CharacterCountedTextarea(), 'detailed_description': SimpleMDEWidget(), 'outline': SimpleMDEWidget(), 'supplementary': SimpleMDEWidget(), } help_texts = { 'abstract': _("<p><a href='#' data-toggle='modal' " "data-target='#proposalFieldExampleModal' " "data-content='abstract'>Proposal Examples</a>.</p>" "<p>The overview of what the talk is about. If the talk " "assume some domain knowledge please state it here. If your " "talk is accepted, this will be displayed on both the website " "and the handbook. Should be one paragraph.</p>"), 'python_level': format_html_lazy( _("The choice of talk level matters during the review " "process. More definition of talk level can be found at the " "<a href='{speaking_talk_url}' target='_blank'>" "How to Propose a Talk</a> page. Note that a proposal won't " "be more likely to be accepted because of being 'Novice' " "level. We may contact you to change the talk level when " "we find the content is too-hard or too-easy for the " "target audience."), speaking_talk_url=reverse_lazy( 'page', kwargs={'path': 'speaking/talk'}, ), ), 'detailed_description': _("<p><a href='#' data-toggle='modal' " "data-target='#proposalFieldExampleModal' " "data-content='detailed description'>" "Proposal Examples</a>.</p>" "<p>Try not be too lengthy to scare away reviewers or " "potential audience. A comfortable length is less than 2000 " "characters (or about 1200 Chinese characters). Since most " "reviewers may not understand the topic as deep as you do, " "including related links to the talk topic will help " "reviewers understand the proposal. Edit using " "<a href='http://daringfireball.net/projects/markdown/basics' " "target='_blank' rel='noopener'>Markdown</a>."), 'recording_policy': format_html_lazy(_( "Whether you agree to give permission to PyCon Taiwan to " "record, edit, and release audio and video of your " "presentation. More information can be found at " "<a href='{recording_policy_url}' target='_blank'>" "Recording Release</a> page."), recording_policy_url=reverse_lazy( 'page', kwargs={'path': 'speaking/recording'}, )), 'slide_link': _("You can add your slide link near or after the conference " "day. Not required for review."), 'referring_policy': _("Whether you agree to give permission to PyCon Taiwan to " "refer your talk proposal to local communities if your " "proposal did not get accepted."), 'objective': _("<p><a href='#' data-toggle='modal' " "data-target='#proposalFieldExampleModal' " "data-content='objective'>Proposal Examples</a>.</p>" "<p>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.</p>"), 'supplementary': _("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' rel='noopener'>Markdown</a>."), 'outline': _("<p><a href='#' data-toggle='modal' " "data-target='#proposalFieldExampleModal' " "data-content='outline'>Proposal Examples</a>.</p>" "<p>How the talk will be arranged. It is highly recommended " "to attach the estimated time length for each sections in the " "talk. Talks in favor of 45min should have a fallback plan " "about how to shrink the content into a 30min one. Edit using " "<a href='http://daringfireball.net/projects/markdown/basics' " "target='_blank' rel='noopener'>Markdown</a>. " "This is NOT made public and for REVIEW ONLY.</p>"), 'remoting_policy': _("Whether you agree to deliver the talk remotely if the epidemic " "(coronavirus) is still raging this August?"), }
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
class Meta: model = TutorialProposal widgets = { 'abstract': CharacterCountedTextarea(), 'objective': CharacterCountedTextarea(), 'detailed_description': SimpleMDEWidget(), 'outline': SimpleMDEWidget(), 'supplementary': SimpleMDEWidget(), } help_texts = { 'duration': _("<p>Tutorials are limited to 1.5 hours in length this year.</p>" ), 'abstract': _("<p><a href='#' data-toggle='modal' " "data-target='#proposalFieldExampleModal' " "data-content='abstract'>Proposal Examples</a>.</p>" "<p>The overview of what the talk is about. If the talk " "assume some domain knowledge please state it here. If your " "talk is accepted, this will be displayed on both the website " "and the handbook. Should be one paragraph.</p>"), 'python_level': format_html_lazy( _("The choice of talk level matters during the review " "process. More definition of talk level can be found at the " "<a href='{frontend_host}/en-us/speaking/talk' target='_blank'>" "How to Propose a Talk</a> page. Note that a proposal won't " "be more likely to be accepted because of being 'Novice' " "level. We may contact you to change the talk level when " "we find the content is too-hard or too-easy for the " "target audience."), frontend_host=f"{settings.FRONTEND_HOST}", ), 'detailed_description': _("<p><a href='#' data-toggle='modal' " "data-target='#proposalFieldExampleModal' " "data-content='detailed description'>" "Proposal Examples</a>.</p>" "<p>Try not be too lengthy to scare away reviewers or " "potential audience. A comfortable length is less than 2000 " "characters (or about 1200 Chinese characters). Since most " "reviewers may not understand the topic as deep as you do, " "including related links to the talk topic will help " "reviewers understand the proposal. Edit using " "<a href='http://daringfireball.net/projects/markdown/basics' " "target='_blank' rel='noopener'>Markdown</a>."), 'recording_policy': format_html_lazy(_( "Whether you agree to give permission to PyCon Taiwan to " "record, edit, and release audio and video of your " "presentation. More information can be found at " "<a href='{frontend_host}/en-us/speaking/recording' target='_blank'>" "Recording Release</a> page."), frontend_host=f"{settings.FRONTEND_HOST}"), 'slide_link': _("You can add your slide link near or after the conference " "day. Not required for review."), 'objective': _("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': _("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' rel='noopener'>Markdown</a>."), 'outline': _("How the tutorial will be arranged. You should enumerate over " "each section in your talk and attach each section with the " "estimated time length. Edit using " "<a href='http://daringfireball.net/projects/markdown/basics' " "target='_blank' rel='noopener'>Markdown</a>. " "This is NOT made public and for REVIEW ONLY."), 'remoting_policy': _("Whether you agree to deliver the tutorial remotely if the epidemic " "(coronavirus) is still raging this August?"), 'live_stream_policy': _("If you disagree to authorize PyCon Taiwan to record video, " "whether you agree to let PyCon Taiwan live stream your presentation " "only for the remote attendees to watch ? (we will not release " "audio and video of your presentation)"), }
class KeynoteEvent(BaseEvent): speaker_name = models.CharField( verbose_name=_('speaker name'), max_length=100, default='', ) speaker_bio = models.TextField( verbose_name=_('speaker bio'), default='', ) speaker_photo = models.ImageField( verbose_name=_('speaker photo'), default='', upload_to=photo_upload_to, storage=select_storage, help_text=_("Raster format of the speaker's photo, e.g. PNG, JPEG."), ) session_title = models.CharField( verbose_name=_('keynote session title'), max_length=140, default='', ) session_description = models.TextField( verbose_name=_('keynote session description'), default='', ) session_slides = models.URLField(verbose_name=_('session slides'), blank=True) slido = models.URLField(verbose_name=_('slido'), blank=True) social_linkedin = models.URLField(verbose_name=_('social linkedin'), blank=True) social_twitter = models.URLField(verbose_name=_('social twitter'), blank=True) social_github = models.URLField(verbose_name=_('social github'), blank=True) slug = models.SlugField( verbose_name=_('slug'), help_text=format_html_lazy( _("This is used to link to the speaker's introduction on the " "Keynote page, e.g. 'liang2' will link to " "'{link}#keynote-speaker-liang2'."), link=reverse_lazy('page', kwargs={'path': 'conference/keynotes'}), )) is_remote = models.BooleanField( verbose_name=_('is remote'), default=False, ) youtube_id = models.CharField( verbose_name=_('youtube id'), max_length=20, blank=True, ) class Meta: verbose_name = _('keynote event') verbose_name_plural = _('keynote events') def __str__(self): return gettext('Keynote: {speaker}'.format( speaker=self.speaker_name, )) def get_absolute_url(self): url = reverse('page', kwargs={'path': 'conference/keynotes'}) split = urllib.parse.urlsplit(url) frag = 'keynote-speaker-{slug}'.format(slug=self.slug) return urllib.parse.urlunsplit(split._replace(fragment=frag)) def get_static_data(self): path = '/'.join([ settings.CONFERENCE_DEFAULT_SLUG, 'assets/keynotes', f'{self.slug}.json', ]) keynote_info = finders.find(path) if not keynote_info: raise FileNotFoundError(path) with open(keynote_info) as f: data = json.load(f) return data def get_static_data_for_locale(self, code=None): if code is None: code = get_language() code = code.split('-', 1)[0] data = self.get_static_data() data = { k: v[code] if isinstance(v, dict) and code in v else v for k, v in data.items() } return data
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, )
class AbstractProposal(models.Model): submitter = models.ForeignKey( to=settings.AUTH_USER_MODEL, verbose_name=_('submitter'), ) title = models.CharField( verbose_name=_('title'), max_length=140, ) CATEGORY_CHOICES = ( ('PRAC', _('Best Practices & Patterns')), ('COM', _('Community')), ('DB', _('Databases')), ('DATA', _('Data Analysis')), ('EDU', _('Education')), ('EMBED', _('Embedded Systems')), ('GAME', _('Gaming')), ('GRAPH', _('Graphics')), ('OTHER', _('Other')), ('CORE', _('Python Core (language, stdlib, etc.)')), ('INTNL', _('Python Internals')), ('LIBS', _('Python Libraries')), ('SCI', _('Science')), ('SEC', _('Security')), ('ADMIN', _('Systems Administration')), ('TEST', _('Testing')), ('WEB', _('Web Frameworks')), ) category = models.CharField( verbose_name=_('category'), max_length=5, choices=CATEGORY_CHOICES, ) LANGUAGE_CHOICES = ( ('ENG', _('English')), ('CHI', _('Chinese')), ) language = models.CharField( verbose_name=_('language'), max_length=3, choices=LANGUAGE_CHOICES, ) target_audience = models.CharField( verbose_name=_('target audience'), max_length=140, help_text=_( "Who is the intended audience for your talk? (Be specific, " "\"Python users\" is not a good answer)"), ) abstract = models.TextField( verbose_name=_('abstract'), max_length=500, help_text=_( "The overview of what the talk is about. If the talk assume some " "domain knowledge please state it here. If your talk is accepted, " "this will be displayed on both the website and the handbook. " "Should be one paragraph."), ) PYTHON_LVL_CHOICES = ( ('NOVICE', _('Novice')), ('INTERMEDIATE', _('Intermediate')), ('EXPERIENCED', _('Experienced')), ) python_level = models.CharField( verbose_name=_('Python level'), max_length=12, choices=PYTHON_LVL_CHOICES, help_text=format_html_lazy( _("The choice of talk level matters during the review process. " "More definition of talk level can be found at the <a href=\"" "{speaking_talk_url}\" target=\"_blank\">How to Propose a " "Talk</a> page. Note that a proposal won't be more likely to be " "accepted because of being \"Novice\" level. We may contact you " "to change the talk level when we find the content is too-hard " "or too-easy for the target audience."), speaking_talk_url=reverse_lazy( 'page', kwargs={'path': 'speaking/talk'}, ), ), ) objective = models.TextField( verbose_name=_('objective'), max_length=500, help_text=_( "What will the attendees get out of your talk? When they leave " "the room, what will they learn that they didn't know before?"), ) detailed_description = models.TextField( verbose_name=_('detailed description'), blank=True, help_text=_( "Try not be too lengthy to scare away reviewers or potential " "audience. A comfortable length is less than 1000 characters " "(or about 600 Chinese characters). Since most reviewers may not " "understand the topic as deep as you do, including related links " "to the talk topic will help reviewers understand the proposal. " "Edit using " "<a href='http://daringfireball.net/projects/markdown/basics' " "target='_blank'>Markdown</a>."), ) 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. Edit using " "<a href='http://daringfireball.net/projects/markdown/basics' " "target='_blank'>Markdown</a>."), ) RECORDING_POLICY_CHOICES = ((True, _('Yes')), (False, _('No'))) recording_policy = models.BooleanField( verbose_name=_('recording policy'), default=True, choices=RECORDING_POLICY_CHOICES, help_text=_( "Whether you agree to give permission to PyCon Taiwan to " "record, edit, and release audio and video of your presentation."), ) slide_link = models.URLField( verbose_name=_('slide link'), blank=True, default='', help_text=_( "You can add your slide link near or after the conference day. " "Not required for review."), ) created_at = models.DateTimeField( verbose_name=_('created at'), auto_now_add=True, db_index=True, ) cancelled = models.BooleanField( verbose_name=_('cancelled'), default=False, db_index=True, ) class Meta: abstract = True ordering = ['-created_at'] def __str__(self): return self.title