diff options
author | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2012-11-01 01:04:34 -0300 |
---|---|---|
committer | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2012-11-01 01:04:34 -0300 |
commit | 667bcdb855644f3adbae0a041a15e52516142ab2 (patch) | |
tree | 30c6312ea33186c5c51143455ce715341dee84a7 | |
parent | a8ebdd9c1e5c76a7a9c7ad77ddda74eaa2d79ec4 (diff) | |
download | askbot-667bcdb855644f3adbae0a041a15e52516142ab2.tar.gz askbot-667bcdb855644f3adbae0a041a15e52516142ab2.tar.bz2 askbot-667bcdb855644f3adbae0a041a15e52516142ab2.zip |
removed unused files
-rw-r--r-- | askbot/forms.py.orig | 1629 | ||||
-rw-r--r-- | askbot/mail/__init__.py.orig | 431 |
2 files changed, 0 insertions, 2060 deletions
diff --git a/askbot/forms.py.orig b/askbot/forms.py.orig deleted file mode 100644 index d1f4a31b..00000000 --- a/askbot/forms.py.orig +++ /dev/null @@ -1,1629 +0,0 @@ -"""Forms, custom form fields and related utility functions -used in AskBot""" -import re -from django import forms -from askbot import const -from askbot.const import message_keys -from django.forms.util import ErrorList -from django.utils.translation import ugettext_lazy as _ -from django.utils.translation import ungettext_lazy, string_concat -from django.utils.text import get_text_list -from django.contrib.auth.models import User -from django_countries import countries -from askbot.utils.forms import NextUrlField, UserNameField -from askbot.mail import extract_first_email_address -from recaptcha_works.fields import RecaptchaField -from askbot.conf import settings as askbot_settings -from askbot.conf import get_tag_display_filter_strategy_choices -from tinymce.widgets import TinyMCE -import logging - - -def cleanup_dict(dictionary, key, empty_value): - """deletes key from dictionary if it exists - and the corresponding value equals the empty_value - """ - if key in dictionary and dictionary[key] == empty_value: - del dictionary[key] - - -def format_form_errors(form): - """Formats form errors in HTML - if there is only one error - returns a plain string - if more than one, returns an unordered list of errors - in HTML format. - If there are no errors, returns empty string - """ - if form.errors: - errors = form.errors.values() - if len(errors) == 1: - return errors[0] - else: - result = '<ul>' - for error in errors: - result += '<li>%s</li>' % error - result += '</ul>' - return result - else: - return '' - - -def clean_marked_tagnames(tagnames): - """return two strings - one containing tagnames - that are straight names of tags, and the second one - containing names of wildcard tags, - wildcard tags are those that have an asterisk at the end - the function does not verify that the tag names are valid - """ - if askbot_settings.USE_WILDCARD_TAGS is False: - return tagnames, list() - - pure_tags = list() - wildcards = list() - for tagname in tagnames: - if tagname == '': - continue - if tagname.endswith('*'): - if tagname.count('*') > 1: - continue - else: - wildcards.append(tagname) - else: - pure_tags.append(tagname) - - return pure_tags, wildcards - - -def filter_choices(remove_choices=None, from_choices=None): - """a utility function that will remove choice tuples - usable for the forms.ChoicesField from - ``from_choices``, the removed ones will be those given - by the ``remove_choice`` list - - there is no error checking, ``from_choices`` tuple must be as expected - to work with the forms.ChoicesField - """ - - if not isinstance(remove_choices, list): - raise TypeError('remove_choices must be a list') - - filtered_choices = tuple() - for choice_to_test in from_choices: - remove = False - for choice in remove_choices: - if choice == choice_to_test[0]: - remove = True - break - if remove is False: - filtered_choices += (choice_to_test, ) - - return filtered_choices - - -def need_mandatory_tags(): - """true, if list of mandatory tags is not empty""" - from askbot import models - return ( - askbot_settings.TAGS_ARE_REQUIRED - and len(models.tag.get_mandatory_tags()) > 0 - ) - - -def mandatory_tag_missing_in_list(tag_strings): - """true, if mandatory tag is not present in the list - of ``tag_strings``""" - from askbot import models - mandatory_tags = models.tag.get_mandatory_tags() - for mandatory_tag in mandatory_tags: - for tag_string in tag_strings: - if tag_strings_match(tag_string, mandatory_tag): - return False - return True - - -def tag_strings_match(tag_string, mandatory_tag): - """true if tag string matches the mandatory tag, - the comparison is not symmetric if tag_string ends with a - wildcard (asterisk) - """ - if mandatory_tag.endswith('*'): - return tag_string.startswith(mandatory_tag[:-1]) - else: - return tag_string == mandatory_tag - - - -COUNTRY_CHOICES = (('unknown', _('select country')),) + countries.COUNTRIES - - -class CountryField(forms.ChoiceField): - """this is better placed into the django_coutries app""" - - def __init__(self, *args, **kwargs): - """sets label and the country choices - """ - kwargs['choices'] = kwargs.pop('choices', COUNTRY_CHOICES) - kwargs['label'] = kwargs.pop('label', _('Country')) - super(CountryField, self).__init__(*args, **kwargs) - - def clean(self, value): - """Handles case of 'unknown' country selection - """ - if self.required: - if value == 'unknown': - raise forms.ValidationError(_('Country field is required')) - if value == 'unknown': - return None - return value - - -class CountedWordsField(forms.CharField): - """a field where a number of words is expected - to be in a certain range""" - - def __init__( - self, min_words=0, max_words=9999, field_name=None, - *args, **kwargs - ): - self.min_words = min_words - self.max_words = max_words - self.field_name = field_name - super(CountedWordsField, self).__init__(*args, **kwargs) - - def clean(self, value): - #todo: this field must be adapted to work with Chinese, etc. - #for that we'll have to count characters instead of words - if value is None: - value = '' - - value = value.strip() - - word_count = len(value.split()) - if word_count < self.min_words: - msg = ungettext_lazy( - 'must be > %d word', - 'must be > %d words', - self.min_words - 1 - ) % (self.min_words - 1) - #todo - space is not used in Chinese - raise forms.ValidationError( - string_concat(self.field_name, ' ', msg) - ) - - if word_count > self.max_words: - msg = ungettext_lazy( - 'must be < %d word', - 'must be < %d words', - self.max_words + 1 - ) % (self.max_words + 1) - raise forms.ValidationError( - string_concat(self.field_name, ' ', msg) - ) - return value - - -class DomainNameField(forms.CharField): - """Field for Internet Domain Names - todo: maybe there is a standard field for this? - """ - def clean(self, value): - #find a better regex, taking into account tlds - domain_re = re.compile(r'[a-zA-Z\d]+(\.[a-zA-Z\d]+)+') - if domain_re.match(value): - return value - else: - raise forms.ValidationError( - '%s is not a valid domain name' % value - ) - - -class TitleField(forms.CharField): - """Fild receiving question title""" - def __init__(self, *args, **kwargs): - super(TitleField, self).__init__(*args, **kwargs) - self.required = kwargs.get('required', True) - self.widget = forms.TextInput( - attrs={'size': 70, 'autocomplete': 'off'} - ) - self.max_length = 255 - self.label = _('title') - self.help_text = _( - 'please enter a descriptive title for your question' - ) - self.initial = '' - - def clean(self, value): - """cleans the field for minimum and maximum length - also is supposed to work for unicode non-ascii characters""" - if value is None: - value = '' - if len(value) < askbot_settings.MIN_TITLE_LENGTH: - msg = ungettext_lazy( - 'title must be > %d character', - 'title must be > %d characters', - askbot_settings.MIN_TITLE_LENGTH - ) % askbot_settings.MIN_TITLE_LENGTH - raise forms.ValidationError(msg) - encoded_value = value.encode('utf-8') - if len(value) == len(encoded_value): - if len(value) > self.max_length: - raise forms.ValidationError( - _( - 'The title is too long, maximum allowed size is ' - '%d characters' - ) % self.max_length - ) - elif len(encoded_value) > self.max_length: - raise forms.ValidationError( - _( - 'The title is too long, maximum allowed size is ' - '%d bytes' - ) % self.max_length - ) - - return value.strip() # TODO: test me - - -class EditorField(forms.CharField): - """EditorField is subclassed by the - :class:`QuestionEditorField` and :class:`AnswerEditorField` - """ - length_error_template_singular = 'post content must be > %d character', - length_error_template_plural = 'post content must be > %d characters', - min_length = 10 # sentinel default value - - def __init__(self, *args, **kwargs): - editor_attrs = kwargs.pop('editor_attrs', {}) - super(EditorField, self).__init__(*args, **kwargs) - self.required = True - widget_attrs = {'id': 'editor'} - if askbot_settings.EDITOR_TYPE == 'markdown': - self.widget = forms.Textarea(attrs=widget_attrs) - elif askbot_settings.EDITOR_TYPE == 'tinymce': - self.widget = TinyMCE(attrs=widget_attrs, mce_attrs=editor_attrs) - self.label = _('content') - self.help_text = u'' - self.initial = '' - - def clean(self, value): - if value is None: - value = '' - if len(value) < self.min_length: - msg = ungettext_lazy( - self.length_error_template_singular, - self.length_error_template_plural, - self.min_length - ) % self.min_length - raise forms.ValidationError(msg) - return value - - -class QuestionEditorField(EditorField): - """Editor field for the questions""" - - def __init__(self, *args, **kwargs): - super(QuestionEditorField, self).__init__(*args, **kwargs) - self.length_error_template_singular = \ - 'question body must be > %d character' - self.length_error_template_plural = \ - 'question body must be > %d characters' - self.min_length = askbot_settings.MIN_QUESTION_BODY_LENGTH - - -class AnswerEditorField(EditorField): - """Editor field for answers""" - - def __init__(self, *args, **kwargs): - super(AnswerEditorField, self).__init__(*args, **kwargs) - self.length_error_template_singular = 'answer must be > %d character' - self.length_error_template_plural = 'answer must be > %d characters' - self.min_length = askbot_settings.MIN_ANSWER_BODY_LENGTH - - -def clean_tag(tag_name): - """a function that cleans a single tag name""" - tag_length = len(tag_name) - if tag_length > askbot_settings.MAX_TAG_LENGTH: - #singular form is odd in english, but required for pluralization - #in other languages - msg = ungettext_lazy( - #odd but added for completeness - 'each tag must be shorter than %(max_chars)d character', - 'each tag must be shorter than %(max_chars)d characters', - tag_length - ) % {'max_chars': tag_length} - raise forms.ValidationError(msg) - - #todo - this needs to come from settings - tagname_re = re.compile(const.TAG_REGEX, re.UNICODE) - if not tagname_re.search(tag_name): - raise forms.ValidationError( - _(message_keys.TAG_WRONG_CHARS_MESSAGE) - ) - - if askbot_settings.FORCE_LOWERCASE_TAGS: - #a simpler way to handle tags - just lowercase thew all - return tag_name.lower() - else: - try: - from askbot import models - stored_tag = models.Tag.objects.get(name__iexact=tag_name) - return stored_tag.name - except models.Tag.DoesNotExist: - return tag_name - - -class TagNamesField(forms.CharField): - """field that receives AskBot tag names""" - - def __init__(self, *args, **kwargs): - super(TagNamesField, self).__init__(*args, **kwargs) - self.required = kwargs.get('required', - askbot_settings.TAGS_ARE_REQUIRED) - self.widget = forms.TextInput( - attrs={'size': 50, 'autocomplete': 'off'} - ) - self.max_length = 255 - self.error_messages['max_length'] = _( - 'We ran out of space for recording the tags. ' - 'Please shorten or delete some of them.' - ) - self.label = _('tags') - self.help_text = ungettext_lazy( - 'Tags are short keywords, with no spaces within. ' - 'Up to %(max_tags)d tag can be used.', - 'Tags are short keywords, with no spaces within. ' - 'Up to %(max_tags)d tags can be used.', - askbot_settings.MAX_TAGS_PER_POST - ) % {'max_tags': askbot_settings.MAX_TAGS_PER_POST} - self.initial = '' - - def clean(self, value): - from askbot import models - value = super(TagNamesField, self).clean(value) - data = value.strip() - if len(data) < 1: - if askbot_settings.TAGS_ARE_REQUIRED: - raise forms.ValidationError( - _(message_keys.TAGS_ARE_REQUIRED_MESSAGE) - ) - else: - #don't test for required characters when tags is '' - return '' - split_re = re.compile(const.TAG_SPLIT_REGEX) - tag_strings = split_re.split(data) - entered_tags = [] - tag_count = len(tag_strings) - if tag_count > askbot_settings.MAX_TAGS_PER_POST: - max_tags = askbot_settings.MAX_TAGS_PER_POST - msg = ungettext_lazy( - 'please use %(tag_count)d tag or less', - 'please use %(tag_count)d tags or less', - tag_count) % {'tag_count': max_tags} - raise forms.ValidationError(msg) - - if need_mandatory_tags(): - if mandatory_tag_missing_in_list(tag_strings): - msg = _( - 'At least one of the following tags is required : %(tags)s' - ) % {'tags': get_text_list(models.tag.get_mandatory_tags())} - raise forms.ValidationError(msg) - - cleaned_entered_tags = list() - for tag in tag_strings: - cleaned_tag = clean_tag(tag) - if cleaned_tag not in cleaned_entered_tags: - cleaned_entered_tags.append(clean_tag(tag)) - - result = u' '.join(cleaned_entered_tags) - - if len(result) > 125:#magic number!, the same as max_length in db - raise forms.ValidationError(self.error_messages['max_length']) - - return u' '.join(cleaned_entered_tags) - - -class WikiField(forms.BooleanField): - """Rendered as checkbox turning post into - "community wiki" - """ - - def __init__(self, *args, **kwargs): - super(WikiField, self).__init__(*args, **kwargs) - self.required = False - self.initial = False - self.label = _( - 'community wiki (karma is not awarded & ' - 'many others can edit wiki post)' - ) - self.help_text = _( - 'if you choose community wiki option, the question ' - 'and answer do not generate points and name of ' - 'author will not be shown' - ) - - def clean(self, value): - return value and askbot_settings.WIKI_ON - - -class EmailNotifyField(forms.BooleanField): - """Rendered as checkbox which turns on - email notifications on the post""" - def __init__(self, *args, **kwargs): - super(EmailNotifyField, self).__init__(*args, **kwargs) - self.required = False - self.widget.attrs['class'] = 'nomargin' - - -class SummaryField(forms.CharField): - - def __init__(self, *args, **kwargs): - super(SummaryField, self).__init__(*args, **kwargs) - self.required = False - self.widget = forms.TextInput( - attrs={'size': 50, 'autocomplete': 'off'} - ) - self.max_length = 300 - self.label = _('update summary:') - self.help_text = _( - 'enter a brief summary of your revision (e.g. ' - 'fixed spelling, grammar, improved style, this ' - 'field is optional)' - ) - - -class EditorForm(forms.Form): - """form with one field - `editor` - the field must be created dynamically, so it's added - in the __init__() function""" - - def __init__(self, editor_attrs=None): - super(EditorForm, self).__init__() - editor_attrs = editor_attrs or {} - self.fields['editor'] = EditorField(editor_attrs=editor_attrs) - - -class DumpUploadForm(forms.Form): - """This form handles importing - data into the forum. At the moment it only - supports stackexchange import. - """ - dump_file = forms.FileField() - - -class ShowQuestionForm(forms.Form): - """Cleans data necessary to access answers and comments - by the respective comment or answer id - necessary - when comments would be normally wrapped and/or displayed - on the page other than the first page of answers to a question. - Same for the answers that are shown on the later pages. - """ - answer = forms.IntegerField(required=False) - comment = forms.IntegerField(required=False) - page = forms.IntegerField(required=False) - sort = forms.CharField(required=False) - - def __init__(self, data, default_sort_method): - super(ShowQuestionForm, self).__init__(data) - self.default_sort_method = default_sort_method - - def get_pruned_data(self): - nones = ('answer', 'comment', 'page') - for key in nones: - if key in self.cleaned_data: - if self.cleaned_data[key] is None: - del self.cleaned_data[key] - if 'sort' in self.cleaned_data: - if self.cleaned_data['sort'] == '': - del self.cleaned_data['sort'] - return self.cleaned_data - - def clean(self): - """this form must always be valid - should use defaults if the data is incomplete - or invalid""" - if self._errors: - #since the form is always valid, clear the errors - logging.error(unicode(self._errors)) - self._errors = {} - - in_data = self.get_pruned_data() - out_data = dict() - if ('answer' in in_data) ^ ('comment' in in_data): - out_data['show_page'] = None - out_data['answer_sort_method'] = 'votes' - out_data['show_comment'] = in_data.get('comment', None) - out_data['show_answer'] = in_data.get('answer', None) - else: - out_data['show_page'] = in_data.get('page', 1) - out_data['answer_sort_method'] = in_data.get( - 'sort', - self.default_sort_method - ) - out_data['show_comment'] = None - out_data['show_answer'] = None - self.cleaned_data = out_data - return out_data - - -class ChangeUserReputationForm(forms.Form): - """Form that allows moderators and site administrators - to adjust reputation of users. - - this form internally verifies that user who claims to - be a moderator acually is - """ - - user_reputation_delta = forms.IntegerField( - min_value=1, - label=_( - 'Enter number of points to add or subtract' - ) - ) - comment = forms.CharField(max_length=128) - - def clean_comment(self): - if 'comment' in self.cleaned_data: - comment = self.cleaned_data['comment'].strip() - if comment == '': - del self.cleaned_data['comment'] - raise forms.ValidationError('Please enter non-empty comment') - self.cleaned_data['comment'] = comment - return comment - -MODERATOR_STATUS_CHOICES = ( - ('a', _('approved')), - ('w', _('watched')), - ('s', _('suspended')), - ('b', _('blocked')), - ) -ADMINISTRATOR_STATUS_CHOICES = (('d', _('administrator')), - ('m', _('moderator')), ) \ - + MODERATOR_STATUS_CHOICES - - -class ChangeUserStatusForm(forms.Form): - """form that allows moderators to change user's status - - the type of options displayed depend on whether user - is a moderator or a site administrator as well as - what is the current status of the moderated user - - for example moderators cannot moderate other moderators - and admins. Admins can take away admin status, but cannot - add it (that can be done through the Django Admin interface - - this form is to be displayed in the user profile under - "moderation" tab - """ - - user_status = forms.ChoiceField(label=_('Change status to')) - - def __init__(self, *arg, **kwarg): - - moderator = kwarg.pop('moderator') - subject = kwarg.pop('subject') - - super(ChangeUserStatusForm, self).__init__(*arg, **kwarg) - - #select user_status_choices depending on status of the moderator - if moderator.is_administrator(): - user_status_choices = ADMINISTRATOR_STATUS_CHOICES - elif moderator.is_moderator(): - user_status_choices = MODERATOR_STATUS_CHOICES - if subject.is_moderator() and subject != moderator: - raise ValueError('moderator cannot moderate another moderator') - else: - raise ValueError('moderator or admin expected from "moderator"') - - #remove current status of the "subject" user from choices - user_status_choices = filter_choices( - remove_choices=[subject.status, ], - from_choices=user_status_choices - ) - - #add prompt option - user_status_choices = (('select', _('which one?')), ) \ - + user_status_choices - - self.fields['user_status'].choices = user_status_choices - - #set prompt option as default - self.fields['user_status'].default = 'select' - self.moderator = moderator - self.subject = subject - - def clean(self): - #if moderator is looking at own profile - do not - #let change status - if 'user_status' in self.cleaned_data: - - user_status = self.cleaned_data['user_status'] - - #does not make sense to change own user status - #if necessary, this can be done from the Django admin interface - if self.moderator == self.subject: - del self.cleaned_data['user_status'] - raise forms.ValidationError(_('Cannot change own status')) - - #do not let moderators turn other users into moderators - if self.moderator.is_moderator() and user_status == 'moderator': - del self.cleanded_data['user_status'] - raise forms.ValidationError( - _('Cannot turn other user to moderator') - ) - - #do not allow moderator to change status of other moderators - if self.moderator.is_moderator() and self.subject.is_moderator(): - del self.cleaned_data['user_status'] - raise forms.ValidationError( - _('Cannot change status of another moderator') - ) - - #do not allow moderator to change to admin - if self.moderator.is_moderator() and user_status == 'd': - raise forms.ValidationError( - _("Cannot change status to admin") - ) - - if user_status == 'select': - del self.cleaned_data['user_status'] - msg = _( - 'If you wish to change %(username)s\'s status, ' - 'please make a meaningful selection.' - ) % {'username': self.subject.username} - raise forms.ValidationError(msg) - - return self.cleaned_data - - -class SendMessageForm(forms.Form): - subject_line = forms.CharField( - label=_('Subject line'), - max_length=64, - widget=forms.TextInput(attrs={'size': 64}, ) - ) - body_text = forms.CharField( - label=_('Message text'), - max_length=1600, - widget=forms.Textarea(attrs={'cols': 64}) - ) - - -class NotARobotForm(forms.Form): - recaptcha = RecaptchaField( - private_key=askbot_settings.RECAPTCHA_SECRET, - public_key=askbot_settings.RECAPTCHA_KEY - ) - - -class FeedbackForm(forms.Form): - name = forms.CharField(label=_('Your name (optional):'), required=False) - email = forms.EmailField(label=_('Email:'), required=False) - message = forms.CharField( - label=_('Your message:'), - max_length=800, - widget=forms.Textarea(attrs={'cols': 60}) - ) - no_email = forms.BooleanField( - label=_("I don't want to give my email or receive a response:"), - required=False - ) - next = NextUrlField() - - def __init__(self, is_auth=False, *args, **kwargs): - super(FeedbackForm, self).__init__(*args, **kwargs) - self.is_auth = is_auth - if not is_auth: - if askbot_settings.USE_RECAPTCHA: - self._add_recaptcha_field() - - def _add_recaptcha_field(self): - self.fields['recaptcha'] = RecaptchaField( - private_key=askbot_settings.RECAPTCHA_SECRET, - public_key=askbot_settings.RECAPTCHA_KEY - ) - - def clean(self): - super(FeedbackForm, self).clean() - if not self.is_auth: - if not self.cleaned_data['no_email'] \ - and not self.cleaned_data['email']: - msg = _('Please mark "I dont want to give my mail" field.') - self._errors['email'] = self.error_class([msg]) - - return self.cleaned_data - - -class FormWithHideableFields(object): - """allows to swap a field widget to HiddenInput() and back""" - - def hide_field(self, name): - """replace widget with HiddenInput() - and save the original in the __hidden_fields dictionary - """ - if not hasattr(self, '__hidden_fields'): - self.__hidden_fields = dict() - if name in self.__hidden_fields: - return - self.__hidden_fields[name] = self.fields[name].widget - self.fields[name].widget = forms.HiddenInput() - - def show_field(self, name): - """restore the original widget on the field - if it was previously hidden - """ - if name in self.__hidden_fields: - self.fields[name] = self.__hidden_fields.pop(name) - - -class PostPrivatelyForm(forms.Form, FormWithHideableFields): - """has a single field `post_privately` with - two related methods""" - - post_privately = forms.BooleanField( - label = _('keep private within your groups'), - required = False - ) - def __init__(self, *args, **kwargs): - user = kwargs.pop('user', None) - self._user = user - super(PostPrivatelyForm, self).__init__(*args, **kwargs) - if self.allows_post_privately() == False: - self.hide_field('post_privately') - - def allows_post_privately(self): - user = self._user - return ( - askbot_settings.GROUPS_ENABLED and \ - user and user.is_authenticated() and \ - user.can_make_group_private_posts() - ) - - def clean_post_privately(self): - if self.allows_post_privately() == False: - self.cleaned_data['post_privately'] = False - return self.cleaned_data['post_privately'] - - -class DraftQuestionForm(forms.Form): - """No real validation required for this form""" - title = forms.CharField(required=False) - text = forms.CharField(required=False) - tagnames = forms.CharField(required=False) - - -class DraftAnswerForm(forms.Form): - """Only thread_id is required""" - thread_id = forms.IntegerField() - text = forms.CharField(required=False) - - -class PostAsSomeoneForm(forms.Form): - post_author_username = forms.CharField( - initial=_('User name:'), - help_text=_( - 'Enter name to post on behalf of someone else. ' - 'Can create new accounts.' - ), - required=False, - widget=forms.TextInput() - ) - post_author_email = forms.CharField( - initial=_('Email address:'), - required=False, - widget=forms.TextInput(attrs={'class': 'tipped-input'}) - ) - - def get_post_user(self, user): - """returns user on whose behalf the post or a revision - is being made - """ - username = self.cleaned_data['post_author_username'] - email= self.cleaned_data['post_author_email'] - if user.is_administrator() and username and email: - post_user = user.get_or_create_fake_user(username, email) - else: - post_user = user - return post_user - - def clean_post_author_username(self): - """if value is the same as initial, it is reset to - empty string - todo: maybe better to have field where initial value is invalid, - then we would not have to have two almost identical clean functions? - """ - username = self.cleaned_data.get('post_author_username', '') - initial_username = unicode(self.fields['post_author_username'].initial) - if username == initial_username: - self.cleaned_data['post_author_username'] = '' - return self.cleaned_data['post_author_username'] - - def clean_post_author_email(self): - """if value is the same as initial, it is reset to - empty string""" - email = self.cleaned_data.get('post_author_email', '') - initial_email = unicode(self.fields['post_author_email'].initial) - if email == initial_email: - email = '' - if email != '': - email = forms.EmailField().clean(email) - self.cleaned_data['post_author_email'] = email - return email - - def clean(self): - """requires email address if user name is given""" - username = self.cleaned_data.get('post_author_username', '') - email = self.cleaned_data.get('post_author_email', '') - if username == '' and email: - username_errors = self._errors.get( - 'post_author_username', - ErrorList() - ) - username_errors.append(_('User name is required with the email')) - self._errors['post_author_username'] = username_errors - raise forms.ValidationError('missing user name') - elif email == '' and username: - email_errors = self._errors.get('post_author_email', ErrorList()) - email_errors.append(_('Email is required if user name is added')) - self._errors['post_author_email'] = email_errors - raise forms.ValidationError('missing email') - - return self.cleaned_data - - -class AskForm(PostAsSomeoneForm, PostPrivatelyForm): - """the form used to askbot questions - field ask_anonymously is shown to the user if the - if ALLOW_ASK_ANONYMOUSLY live setting is True - however, for simplicity, the value will always be present - in the cleaned data, and will evaluate to False if the - settings forbids anonymous asking - """ - title = TitleField() - tags = TagNamesField() - wiki = WikiField() - group_id = forms.IntegerField(required = False, widget = forms.HiddenInput) - ask_anonymously = forms.BooleanField( - label=_('ask anonymously'), - help_text=_( - 'Check if you do not want to reveal your name ' - 'when asking this question' - ), - required=False, - ) - openid = forms.CharField( - required=False, max_length=255, - widget=forms.TextInput(attrs={'size': 40, 'class': 'openid-input'}) - ) - - def __init__(self, *args, **kwargs): - super(AskForm, self).__init__(*args, **kwargs) - #it's important that this field is set up dynamically - self.fields['text'] = QuestionEditorField() - #hide ask_anonymously field - if askbot_settings.ALLOW_ASK_ANONYMOUSLY is False: - self.hide_field('ask_anonymously') - - def clean_ask_anonymously(self): - """returns false if anonymous asking is not allowed - """ - if askbot_settings.ALLOW_ASK_ANONYMOUSLY is False: - self.cleaned_data['ask_anonymously'] = False - return self.cleaned_data['ask_anonymously'] - - -ASK_BY_EMAIL_SUBJECT_HELP = _( - 'Subject line is expected in the format: ' - '[tag1, tag2, tag3,...] question title' -) - -#widgetforms -class AskWidgetForm(forms.Form, FormWithHideableFields): - '''Simple form with just the title to ask a question''' - - title = TitleField() - ask_anonymously = forms.BooleanField( - label=_('ask anonymously'), - help_text=_( - 'Check if you do not want to reveal your name ' - 'when asking this question' - ), - required=False, - ) - - def __init__(self, include_text=True, *args, **kwargs): - super(AskWidgetForm, self).__init__(*args, **kwargs) - #hide ask_anonymously field - if not askbot_settings.ALLOW_ASK_ANONYMOUSLY: - self.hide_field('ask_anonymously') - self.fields['text'] = QuestionEditorField() - if not include_text: - self.hide_field('text') - #hack to make it validate - self.fields['text'].required = False - self.fields['text'].min_length = 0 - -class CreateAskWidgetForm(forms.Form, FormWithHideableFields): - title = forms.CharField(max_length=100) - include_text_field = forms.BooleanField(required=False) - - inner_style = forms.CharField( - widget=forms.Textarea, - required=False - ) - outer_style = forms.CharField( - widget=forms.Textarea, - required=False - ) - - def __init__(self, *args, **kwargs): - from askbot.models import Group, Tag - super(CreateAskWidgetForm, self).__init__(*args, **kwargs) - self.fields['group'] = forms.ModelChoiceField( - queryset=Group.objects.exclude_personal(), - required=False - ) - self.fields['tag'] = forms.ModelChoiceField(queryset=Tag.objects.get_content_tags(), - required=False) - if not askbot_settings.GROUPS_ENABLED: - self.hide_field('group') - -class CreateQuestionWidgetForm(forms.Form, FormWithHideableFields): - title = forms.CharField(max_length=100) - question_number = forms.CharField(initial='7') - tagnames = forms.CharField(label=_('tags'), max_length=50) - search_query = forms.CharField(max_length=50, required=False) - order_by = forms.ChoiceField( - choices=const.SEARCH_ORDER_BY, - initial='-added_at' - ) - style = forms.CharField( - widget=forms.Textarea, - initial=const.DEFAULT_QUESTION_WIDGET_STYLE, - required=False - ) - - def __init__(self, *args, **kwargs): - from askbot.models import Group - super(CreateQuestionWidgetForm, self).__init__(*args, **kwargs) - self.fields['tagnames'] = TagNamesField() - self.fields['group'] = forms.ModelChoiceField( - queryset=Group.objects.exclude(name__startswith='_internal'), - required=False - ) - -class AskByEmailForm(forms.Form): - """:class:`~askbot.forms.AskByEmailForm` - validates question data, where question was posted - by email. - - It is ivoked by the management command - :mod:`~askbot.management.commands.post_emailed_questions` - - Input is text data with attributes: - - * :attr:`~askbot.forms.AskByEmailForm.sender` - unparsed "from" data - * :attr:`~askbot.forms.AskByEmailForm.subject` - subject line - * :attr:`~askbot.forms.AskByEmailForm.body_text` - body text of the email - - Cleaned values are: - * ``email`` - email address - * ``title`` - question title - * ``tagnames`` - tag names all in one string - * ``body_text`` - body of question text - - a pass-through, no extra validation - """ - sender = forms.CharField(max_length=255) - subject = forms.CharField( - max_length=255, - error_messages={ - 'required': ASK_BY_EMAIL_SUBJECT_HELP - } - ) - body_text = QuestionEditorField() - - def clean_sender(self): - """Cleans the :attr:`~askbot.forms.AskByEmail.sender` attribute - - If the field is valid, cleaned data will receive value ``email`` - """ - raw_email = self.cleaned_data['sender'] - email = extract_first_email_address(raw_email) - if email is None: - raise forms.ValidationError('Could not extract email address') - self.cleaned_data['email'] = email - return self.cleaned_data['sender'] - - def clean_subject(self): - """Cleans the :attr:`~askbot.forms.AskByEmail.subject` attribute - - If the field is valid, cleaned data will receive values - ``tagnames`` and ``title`` - """ - raw_subject = self.cleaned_data['subject'].strip() - if askbot_settings.TAGS_ARE_REQUIRED: - subject_re = re.compile(r'^\[([^]]+)\](.*)$') - else: - subject_re = re.compile(r'^(?:\[([^]]+)\])?(.*)$') - match = subject_re.match(raw_subject) - if match: - #make raw tags comma-separated - if match.group(1) is None: # no tags - self.cleaned_data['tagnames'] = '' - else: - tagnames = match.group(1).replace(';', ',') - - #pre-process tags - tag_list = [tag.strip() for tag in tagnames.split(',')] - tag_list = [re.sub(r'\s+', ' ', tag) for tag in tag_list] - - if askbot_settings.REPLACE_SPACE_WITH_DASH_IN_EMAILED_TAGS: - tag_list = [tag.replace(' ', '-') for tag in tag_list] - #todo: use tag separator char here - tagnames = ' '.join(tag_list) - - #clean tags - may raise ValidationError - self.cleaned_data['tagnames'] = TagNamesField().clean(tagnames) - - #clean title - may raise ValidationError - title = match.group(2).strip() - self.cleaned_data['title'] = TitleField().clean(title) - else: - raise forms.ValidationError(ASK_BY_EMAIL_SUBJECT_HELP) - return self.cleaned_data['subject'] - - -class AnswerForm(PostAsSomeoneForm, PostPrivatelyForm): - text = AnswerEditorField() - wiki = WikiField() - openid = forms.CharField( - required=False, max_length=255, - widget=forms.TextInput(attrs={'size': 40, 'class': 'openid-input'}) - ) - email_notify = EmailNotifyField(initial=False) - - def __init__(self, *args, **kwargs): - super(AnswerForm, self).__init__(*args, **kwargs) - self.fields['text'] = AnswerEditorField() - self.fields['email_notify'].widget.attrs['id'] = \ - 'question-subscribe-updates' - - -class VoteForm(forms.Form): - """form used in ajax vote view (only comment_upvote so far) - """ - post_id = forms.IntegerField() - # char because it is 'true' or 'false' as string - cancel_vote = forms.CharField() - - def clean_cancel_vote(self): - val = self.cleaned_data['cancel_vote'] - if val == 'true': - result = True - elif val == 'false': - result = False - else: - del self.cleaned_data['cancel_vote'] - raise forms.ValidationError( - 'either "true" or "false" strings expected' - ) - self.cleaned_data['cancel_vote'] = result - return self.cleaned_data['cancel_vote'] - - -class CloseForm(forms.Form): - reason = forms.ChoiceField(choices=const.CLOSE_REASONS) - - -class RetagQuestionForm(forms.Form): - tags = TagNamesField() - - def __init__(self, question, *args, **kwargs): - """initialize the default values""" - super(RetagQuestionForm, self).__init__(*args, **kwargs) - self.fields['tags'].initial = question.thread.tagnames - - -class RevisionForm(forms.Form): - """ - Lists revisions of a Question or Answer - """ - revision = forms.ChoiceField( - widget=forms.Select( - attrs={'style': 'width:520px'} - ) - ) - - def __init__(self, post, latest_revision, *args, **kwargs): - super(RevisionForm, self).__init__(*args, **kwargs) - revisions = post.revisions.values_list( - 'revision', 'author__username', 'revised_at', 'summary' - ) - date_format = '%c' - rev_choices = list() - for r in revisions: - rev_details = u'%s - %s (%s) %s' % ( - r[0], r[1], r[2].strftime(date_format), r[3] - ) - rev_choices.append((r[0], rev_details)) - - self.fields['revision'].choices = rev_choices - self.fields['revision'].initial = latest_revision.revision - -class EditQuestionForm(PostAsSomeoneForm, PostPrivatelyForm): - title = TitleField() - tags = TagNamesField() - summary = SummaryField() - wiki = WikiField() - reveal_identity = forms.BooleanField( - help_text=_( - 'You have asked this question anonymously, ' - 'if you decide to reveal your identity, please check ' - 'this box.' - ), - label=_('reveal identity'), - required=False, - ) - - #todo: this is odd that this form takes question as an argument - def __init__(self, *args, **kwargs): - """populate EditQuestionForm with initial data""" - self.question = kwargs.pop('question') - self.user = kwargs['user']#preserve for superclass - revision = kwargs.pop('revision') - super(EditQuestionForm, self).__init__(*args, **kwargs) - #it is important to add this field dynamically - self.fields['text'] = QuestionEditorField() - self.fields['title'].initial = revision.title - self.fields['text'].initial = revision.text - self.fields['tags'].initial = revision.tagnames - self.fields['wiki'].initial = self.question.wiki - #hide the reveal identity field - if not self.can_stay_anonymous(): - self.hide_field('reveal_identity') - - def has_changed(self): - if super(EditQuestionForm, self).has_changed(): - return True - if askbot_settings.GROUPS_ENABLED: - return self.question.is_private() \ - != self.cleaned_data['post_privately'] - else: - return False - - def can_stay_anonymous(self): - """determines if the user cat keep editing the question - anonymously""" - return (askbot_settings.ALLOW_ASK_ANONYMOUSLY - and self.question.is_anonymous - and self.user.is_owner_of(self.question) - ) - - def clean_reveal_identity(self): - """cleans the reveal_identity field - which determines whether previous anonymous - edits must be rewritten as not anonymous - this does not necessarily mean that the edit will be anonymous - - only does real work when question is anonymous - based on the following truth table: - - is_anon can owner checked cleaned data - - * * * False (ignore choice in checkbox) - + + + + True - + + + - False - + + - + Raise(Not owner) - + + - - False - + - + + True (setting "can" changed, say yes) - + - + - False, warn (but prev edits stay anon) - + - - + Raise(Not owner) - + - - - False - """ - value = self.cleaned_data['reveal_identity'] - if self.question.is_anonymous: - if value is True: - if self.user.is_owner_of(self.question): - #regardless of the ALLOW_ASK_ANONYMOUSLY - return True - else: - self.show_field('reveal_identity') - del self.cleaned_data['reveal_identity'] - raise forms.ValidationError( - _( - 'Sorry, only owner of the anonymous ' - 'question can reveal his or her ' - 'identity, please uncheck the ' - 'box' - ) - ) - else: - can_ask_anon = askbot_settings.ALLOW_ASK_ANONYMOUSLY - is_owner = self.user.is_owner_of(self.question) - if can_ask_anon is False and is_owner: - self.show_field('reveal_identity') - raise forms.ValidationError( - _( - 'Sorry, apparently rules have just changed - ' - 'it is no longer possible to ask anonymously. ' - 'Please either check the "reveal identity" box ' - 'or reload this page and try editing the question ' - 'again.' - ) - ) - return False - else: - #takes care of 8 possibilities - first row of the table - return False - - def clean(self): - """Purpose of this function is to determine whether - it is ok to apply edit anonymously in the synthetic - field edit_anonymously. It relies on correct cleaning - if the "reveal_identity" field - """ - super(EditQuestionForm, self).clean() - reveal_identity = self.cleaned_data.get('reveal_identity', False) - stay_anonymous = False - if reveal_identity is False and self.can_stay_anonymous(): - stay_anonymous = True - self.cleaned_data['stay_anonymous'] = stay_anonymous - return self.cleaned_data - -class EditAnswerForm(PostAsSomeoneForm, PostPrivatelyForm): - summary = SummaryField() - wiki = WikiField() - - def __init__(self, answer, revision, *args, **kwargs): - self.answer = answer - super(EditAnswerForm, self).__init__(*args, **kwargs) - #it is important to add this field dynamically - self.fields['text'] = AnswerEditorField() - self.fields['text'].initial = revision.text - self.fields['wiki'].initial = answer.wiki - - def has_changed(self): - #todo: this function is almost copy/paste of EditQuestionForm.has_changed() - if super(EditAnswerForm, self).has_changed(): - return True - if askbot_settings.GROUPS_ENABLED: - return self.answer.is_private() \ - != self.cleaned_data['post_privately'] - else: - return False - - -class EditTagWikiForm(forms.Form): - text = forms.CharField(required=False) - tag_id = forms.IntegerField() - - -class EditUserForm(forms.Form): - email = forms.EmailField( - label=u'Email', - required=True, - max_length=255, - widget=forms.TextInput(attrs={'size': 35}) - ) - - realname = forms.CharField( - label=_('Real name'), - required=False, - max_length=255, - widget=forms.TextInput(attrs={'size': 35}) - ) - - website = forms.URLField( - label=_('Website'), - required=False, - max_length=255, - widget=forms.TextInput(attrs={'size': 35}) - ) - - city = forms.CharField( - label=_('City'), - required=False, - max_length=255, - widget=forms.TextInput(attrs={'size': 35}) - ) - - country = CountryField(required=False) - - show_country = forms.BooleanField( - label=_('Show country'), - required=False - ) - - show_marked_tags = forms.BooleanField( - label=_('Show tag choices'), - required=False - ) - - birthday = forms.DateField( - label=_('Date of birth'), - help_text=_( - 'will not be shown, used to calculate ' - 'age, format: YYYY-MM-DD' - ), - required=False, - widget=forms.TextInput(attrs={'size': 35}) - ) - - about = forms.CharField( - label=_('Profile'), - required=False, - widget=forms.Textarea(attrs={'cols': 60}) - ) - - def __init__(self, user, *args, **kwargs): - super(EditUserForm, self).__init__(*args, **kwargs) - logging.debug('initializing the form') - if askbot_settings.EDITABLE_SCREEN_NAME: - self.fields['username'] = UserNameField(label=_('Screen name')) - self.fields['username'].initial = user.username - self.fields['username'].user_instance = user - self.fields['email'].initial = user.email - self.fields['realname'].initial = user.real_name - self.fields['website'].initial = user.website - self.fields['city'].initial = user.location - if user.country is None: - country = 'unknown' - else: - country = user.country - self.fields['country'].initial = country - self.fields['show_country'].initial = user.show_country - self.fields['show_marked_tags'].initial = user.show_marked_tags - - if user.date_of_birth is not None: - self.fields['birthday'].initial = user.date_of_birth - - self.fields['about'].initial = user.about - self.user = user - - def clean_email(self): - """For security reason one unique email in database""" - if self.user.email != self.cleaned_data['email']: - #todo dry it, there is a similar thing in openidauth - if askbot_settings.EMAIL_UNIQUE is True: - if 'email' in self.cleaned_data: - try: - User.objects.get(email=self.cleaned_data['email']) - except User.DoesNotExist: - return self.cleaned_data['email'] - except User.MultipleObjectsReturned: - raise forms.ValidationError(_( - 'this email has already been registered, ' - 'please use another one') - ) - raise forms.ValidationError(_( - 'this email has already been registered, ' - 'please use another one') - ) - return self.cleaned_data['email'] - - -class TagFilterSelectionForm(forms.ModelForm): - email_tag_filter_strategy = forms.ChoiceField( - initial = const.EXCLUDE_IGNORED, - label = _('Choose email tag filter'), - widget = forms.RadioSelect - ) - def __init__(self, *args, **kwargs): - super(TagFilterSelectionForm, self).__init__(*args, **kwargs) - choices = get_tag_display_filter_strategy_choices() - self.fields['email_tag_filter_strategy'].choices = choices - - class Meta: - model = User - fields = ('email_tag_filter_strategy',) - - def save(self): - before = self.instance.email_tag_filter_strategy - super(TagFilterSelectionForm, self).save() - after = self.instance.email_tag_filter_strategy - if before != after: - return True - return False - - -class EmailFeedSettingField(forms.ChoiceField): - def __init__(self, *arg, **kwarg): - kwarg['choices'] = const.NOTIFICATION_DELIVERY_SCHEDULE_CHOICES - kwarg['widget'] = forms.RadioSelect - super(EmailFeedSettingField, self).__init__(*arg, **kwarg) - - -class EditUserEmailFeedsForm(forms.Form): - FORM_TO_MODEL_MAP = { - 'all_questions': 'q_all', - 'asked_by_me': 'q_ask', - 'answered_by_me': 'q_ans', - 'individually_selected': 'q_sel', - 'mentions_and_comments': 'm_and_c', - } - NO_EMAIL_INITIAL = { - 'all_questions': 'n', - 'asked_by_me': 'n', - 'answered_by_me': 'n', - 'individually_selected': 'n', - 'mentions_and_comments': 'n', - } - INSTANT_EMAIL_INITIAL = { - 'all_questions': 'i', - 'asked_by_me': 'i', - 'answered_by_me': 'i', - 'individually_selected': 'i', - 'mentions_and_comments': 'i', - } - - asked_by_me = EmailFeedSettingField( - label=_('Asked by me') - ) - answered_by_me = EmailFeedSettingField( - label=_('Answered by me') - ) - individually_selected = EmailFeedSettingField( - label=_('Individually selected') - ) - all_questions = EmailFeedSettingField( - label=_('Entire forum (tag filtered)'), - ) - - mentions_and_comments = EmailFeedSettingField( - label=_('Comments and posts mentioning me'), - ) - - def set_initial_values(self, user=None): - from askbot import models - KEY_MAP = dict([(v, k) for k, v in self.FORM_TO_MODEL_MAP.iteritems()]) - if user is not None: - settings = models.EmailFeedSetting.objects.filter(subscriber=user) - initial_values = {} - for setting in settings: - feed_type = setting.feed_type - form_field = KEY_MAP[feed_type] - frequency = setting.frequency - initial_values[form_field] = frequency - self.initial = initial_values - return self - - def reset(self): - """equivalent to set_frequency('n') - but also returns self due to some legacy requirement - todo: clean up use of this function - """ - if self.is_bound: - self.cleaned_data = self.NO_EMAIL_INITIAL - self.initial = self.NO_EMAIL_INITIAL - return self - - def get_db_model_subscription_type_names(self): - """todo: refactor this - too hacky - should probably use model form instead - - returns list of values acceptable in - ``attr::models.user.EmailFeedSetting.feed_type`` - """ - return self.FORM_TO_MODEL_MAP.values() - - def set_frequency(self, frequency='n'): - data = { - 'all_questions': frequency, - 'asked_by_me': frequency, - 'answered_by_me': frequency, - 'individually_selected': frequency, - 'mentions_and_comments': frequency - } - if self.is_bound: - self.cleaned_data = data - self.initial = data - - def save(self, user, save_unbound=False): - """with save_unbound==True will bypass form - validation and save initial values - """ - from askbot import models - changed = False - for form_field, feed_type in self.FORM_TO_MODEL_MAP.items(): - s, created = models.EmailFeedSetting.objects.get_or_create( - subscriber=user, - feed_type=feed_type - ) - if save_unbound: - #just save initial values instead - if form_field in self.initial: - new_value = self.initial[form_field] - else: - new_value = self.fields[form_field].initial - else: - new_value = self.cleaned_data[form_field] - if s.frequency != new_value: - s.frequency = new_value - s.save() - changed = True - else: - if created: - s.save() - if form_field == 'individually_selected': - user.followed_threads.clear() - return changed - - -class SubscribeForEmailUpdatesField(forms.ChoiceField): - """a simple yes or no field to subscribe for email or not""" - def __init__(self, **kwargs): - kwargs['widget'] = forms.widgets.RadioSelect - kwargs['error_messages'] = { - 'required': _('please choose one of the options above') - } - kwargs['choices'] = ( - ('y', _('okay, let\'s try!')), - ( - 'n', - _('no %(sitename)s email please, thanks') - % {'sitename': askbot_settings.APP_SHORT_NAME} - ) - ) - super(SubscribeForEmailUpdatesField, self).__init__(**kwargs) - - -class SimpleEmailSubscribeForm(forms.Form): - subscribe = SubscribeForEmailUpdatesField() - - def save(self, user=None): - EFF = EditUserEmailFeedsForm - #here we have kind of an anomaly - the value 'y' is redundant - #with the frequency variable - needs to be fixed - if self.is_bound and self.cleaned_data['subscribe'] == 'y': - email_settings_form = EFF() - email_settings_form.set_initial_values(user) - logging.debug('%s wants to subscribe' % user.username) - else: - email_settings_form = EFF(initial=EFF.NO_EMAIL_INITIAL) - email_settings_form.save(user, save_unbound=True) - - -class GroupLogoURLForm(forms.Form): - """form for saving group logo url""" - group_id = forms.IntegerField() - image_url = forms.CharField() - - -class EditGroupMembershipForm(forms.Form): - """a form for adding or removing users - to and from user groups""" - user_id = forms.IntegerField() - group_name = forms.CharField() - action = forms.CharField() - - def clean_action(self): - """allowed actions are 'add' and 'remove'""" - action = self.cleaned_data['action'] - if action not in ('add', 'remove'): - del self.cleaned_data['action'] - raise forms.ValidationError('invalid action') - return action - - -class EditRejectReasonForm(forms.Form): - reason_id = forms.IntegerField(required=False) - title = CountedWordsField( - min_words=1, max_words=4, field_name=_('Title') - ) - details = CountedWordsField( - min_words=6, field_name=_('Description') - ) - -class ModerateTagForm(forms.Form): - tag_id = forms.IntegerField() - thread_id = forms.IntegerField(required = False) - action = forms.CharField() - - def clean_action(self): - action = self.cleaned_data['action'] - assert(action in ('accept', 'reject')) - return action - -class ShareQuestionForm(forms.Form): - thread_id = forms.IntegerField() - recipient_name = forms.CharField() diff --git a/askbot/mail/__init__.py.orig b/askbot/mail/__init__.py.orig deleted file mode 100644 index 7d426e80..00000000 --- a/askbot/mail/__init__.py.orig +++ /dev/null @@ -1,431 +0,0 @@ -"""functions that send email in askbot -these automatically catch email-related exceptions -""" -import os -import smtplib -import logging -from django.core import mail -from django.conf import settings as django_settings -from django.core.exceptions import PermissionDenied -from django.forms import ValidationError -from django.utils.translation import ugettext_lazy as _ -from django.utils.translation import string_concat -from django.template import Context -from django.utils.html import strip_tags -from askbot import exceptions -from askbot import const -from askbot.conf import settings as askbot_settings -from askbot.utils import url_utils -from askbot.utils.file_utils import store_file -from askbot.utils.html import absolutize_urls - -from bs4 import BeautifulSoup -#todo: maybe send_mail functions belong to models -#or the future API -def prefix_the_subject_line(subject): - """prefixes the subject line with the - EMAIL_SUBJECT_LINE_PREFIX either from - from live settings, which take default from django - """ - prefix = askbot_settings.EMAIL_SUBJECT_PREFIX - if prefix != '': - subject = prefix.strip() + ' ' + subject.strip() - return subject - -def extract_first_email_address(text): - """extract first matching email address - from text string - returns ``None`` if there are no matches - """ - match = const.EMAIL_REGEX.search(text) - if match: - return match.group(0) - else: - return None - -def thread_headers(post, orig_post, update): - """modify headers for email messages, so - that emails appear as threaded conversations in gmail""" - suffix_id = django_settings.SERVER_EMAIL - if update == const.TYPE_ACTIVITY_ASK_QUESTION: - msg_id = "NQ-%s-%s" % (post.id, suffix_id) - headers = {'Message-ID': msg_id} - elif update == const.TYPE_ACTIVITY_ANSWER: - msg_id = "NA-%s-%s" % (post.id, suffix_id) - orig_id = "NQ-%s-%s" % (orig_post.id, suffix_id) - headers = {'Message-ID': msg_id, - 'In-Reply-To': orig_id} - elif update == const.TYPE_ACTIVITY_UPDATE_QUESTION: - msg_id = "UQ-%s-%s-%s" % (post.id, post.last_edited_at, suffix_id) - orig_id = "NQ-%s-%s" % (orig_post.id, suffix_id) - headers = {'Message-ID': msg_id, - 'In-Reply-To': orig_id} - elif update == const.TYPE_ACTIVITY_COMMENT_QUESTION: - msg_id = "CQ-%s-%s" % (post.id, suffix_id) - orig_id = "NQ-%s-%s" % (orig_post.id, suffix_id) - headers = {'Message-ID': msg_id, - 'In-Reply-To': orig_id} - elif update == const.TYPE_ACTIVITY_UPDATE_ANSWER: - msg_id = "UA-%s-%s-%s" % (post.id, post.last_edited_at, suffix_id) - orig_id = "NQ-%s-%s" % (orig_post.id, suffix_id) - headers = {'Message-ID': msg_id, - 'In-Reply-To': orig_id} - elif update == const.TYPE_ACTIVITY_COMMENT_ANSWER: - msg_id = "CA-%s-%s" % (post.id, suffix_id) - orig_id = "NQ-%s-%s" % (orig_post.id, suffix_id) - headers = {'Message-ID': msg_id, - 'In-Reply-To': orig_id} - else: - # Unknown type -> Can't set headers - return {} - - return headers - -def clean_html_email(email_body): - """needs more clenup might not work for other email templates - that do not use table layout - """ - soup = BeautifulSoup(email_body) - body_element = soup.find('body') - phrases = map( - lambda s: s.strip(), - filter(bool, body_element.get_text().split('\n')) - ) - return '\n\n'.join(phrases) - -def send_mail( - subject_line = None, - body_text = None, - from_email = django_settings.DEFAULT_FROM_EMAIL, - recipient_list = None, - activity_type = None, - related_object = None, - headers = None, - raise_on_failure = False, - ): - """ - todo: remove parameters not relevant to the function - sends email message - logs email sending activity - and any errors are reported as critical - in the main log file - - related_object is not mandatory, other arguments - are. related_object (if given, will be saved in - the activity record) - - if raise_on_failure is True, exceptions.EmailNotSent is raised - """ - body_text = absolutize_urls(body_text) - try: - assert(subject_line is not None) - subject_line = prefix_the_subject_line(subject_line) - msg = mail.EmailMultiAlternatives( - subject_line, - clean_html_email(body_text), - from_email, - recipient_list, - headers = headers - ) - msg.attach_alternative(body_text, "text/html") - msg.send() - if related_object is not None: - assert(activity_type is not None) - except Exception, error: - logging.critical(unicode(error)) - if raise_on_failure == True: - raise exceptions.EmailNotSent(unicode(error)) - -def mail_moderators( - subject_line = '', - body_text = '', - raise_on_failure = False, - headers = None - ): - """sends email to forum moderators and admins - """ - body_text = absolutize_urls(body_text) - from django.db.models import Q - from askbot.models import User - recipient_list = User.objects.filter( - Q(status='m') | Q(is_superuser=True) - ).filter( - is_active = True - ).values_list('email', flat=True) - recipient_list = set(recipient_list) - - from_email = '' - if hasattr(django_settings, 'DEFAULT_FROM_EMAIL'): - from_email = django_settings.DEFAULT_FROM_EMAIL - - try: - msg = mail.EmailMessage( - subject_line, - body_text, - from_email, - recipient_list, - headers = headers or {} - ) - msg.content_subtype = 'html' - msg.send() - except smtplib.SMTPException, error: - logging.critical(unicode(error)) - if raise_on_failure == True: - raise exceptions.EmailNotSent(unicode(error)) - -INSTRUCTIONS_PREAMBLE = _('<p>To ask by email, please:</p>') -QUESTION_TITLE_INSTRUCTION = _( - '<li>Type title in the subject line</li>' -) -QUESTION_DETAILS_INSTRUCTION = _( - '<li>Type details of your question into the email body</li>' -) -OPTIONAL_TAGS_INSTRUCTION = _( -"""<li>The beginning of the subject line can contain tags, -<em>enclosed in the square brackets</em> like so: [Tag1; Tag2]</li>""" -) -REQUIRED_TAGS_INSTRUCTION = _( -"""<li>In the beginning of the subject add at least one tag -<em>enclosed in the brackets</em> like so: [Tag1; Tag2].</li>""" -) -TAGS_INSTRUCTION_FOOTNOTE = _( -"""<p>Note that a tag may consist of more than one word, to separate -the tags, use a semicolon or a comma, for example, [One tag; Other tag]</p>""" -) - -def bounce_email( - email, subject, reason = None, body_text = None, reply_to = None -): - """sends a bounce email at address ``email``, with the subject - line ``subject``, accepts several reasons for the bounce: - * ``'problem_posting'``, ``unknown_user`` and ``permission_denied`` - * ``body_text`` in an optional parameter that allows to append - extra text to the message - """ - if reason == 'problem_posting': - error_message = _( - '<p>Sorry, there was an error posting your question ' - 'please contact the %(site)s administrator</p>' - ) % {'site': askbot_settings.APP_SHORT_NAME} - - if askbot_settings.TAGS_ARE_REQUIRED: - error_message = string_concat( - INSTRUCTIONS_PREAMBLE, - '<ul>', - QUESTION_TITLE_INSTRUCTION, - REQUIRED_TAGS_INSTRUCTION, - QUESTION_DETAILS_INSTRUCTION, - '</ul>', - TAGS_INSTRUCTION_FOOTNOTE - ) - else: - error_message = string_concat( - INSTRUCTIONS_PREAMBLE, - '<ul>', - QUESTION_TITLE_INSTRUCTION, - QUESTION_DETAILS_INSTRUCTION, - OPTIONAL_TAGS_INSTRUCTION, - '</ul>', - TAGS_INSTRUCTION_FOOTNOTE - ) - - elif reason == 'unknown_user': - error_message = _( - '<p>Sorry, in order to post questions on %(site)s ' - 'by email, please <a href="%(url)s">register first</a></p>' - ) % { - 'site': askbot_settings.APP_SHORT_NAME, - 'url': url_utils.get_login_url() - } - elif reason == 'permission_denied' and body_text is None: - error_message = _( - '<p>Sorry, your question could not be posted ' - 'due to insufficient privileges of your user account</p>' - ) - elif body_text: - error_message = body_text - else: - raise ValueError('unknown reason to bounce an email: "%s"' % reason) - - - #print 'sending email' - #print email - #print subject - #print error_message - headers = {} - if reply_to: - headers['Reply-To'] = reply_to - - send_mail( - recipient_list = (email,), - subject_line = 'Re: ' + subject, - body_text = error_message, - headers = headers - ) - -def extract_reply(text): - """take the part above the separator - and discard the last line above the separator - """ - if const.REPLY_SEPARATOR_REGEX.search(text): - text = const.REPLY_SEPARATOR_REGEX.split(text)[0] - text_lines = text.splitlines(False) - #log last 10 lines of text - to capture email responses - logging.debug('reply-border-separator|' + '|'.join(text_lines[-10:])) - #here we need code stripping the "On ... somebody wrote:" - return '\n'.join(text_lines[:-3]) - else: - return text - -def process_attachment(attachment): - """will save a single - attachment and return - link to file in the markdown format and the - file storage object - """ - file_storage, file_name, file_url = store_file(attachment) - markdown_link = '[%s](%s) ' % (attachment.name, file_url) - file_extension = os.path.splitext(attachment.name)[1] - #todo: this is a hack - use content type - if file_extension.lower() in ('.png', '.jpg', '.jpeg', '.gif'): - markdown_link = '!' + markdown_link - return markdown_link, file_storage - -def extract_user_signature(text, reply_code): - """extracts email signature as text trailing - the reply code""" - striped_text = strip_tags(text) - if reply_code in striped_text: - #extract the signature - tail = list() - for line in reversed(striped_text.splitlines()): - #scan backwards from the end until the magic line - if reply_code in line: - break - tail.insert(0, line) - - #strip off the leading quoted lines, there could be one or two - #also strip empty lines - while tail and (tail[0].startswith('>') or tail[0].strip() == ''): - tail.pop(0) - - return '\n'.join(tail) - else: - return '' - - -def process_parts(parts, reply_code = None): - """Process parts will upload the attachments and parse out the - body, if body is multipart. Secondly - links to attachments - will be added to the body of the question. - Returns ready to post body of the message and the list - of uploaded files. - """ - body_markdown = '' - stored_files = list() - attachments_markdown = '' - for (part_type, content) in parts: - if part_type == 'attachment': - markdown, stored_file = process_attachment(content) - stored_files.append(stored_file) - attachments_markdown += '\n\n' + markdown - elif part_type == 'body': - body_markdown += '\n\n' + content.strip('\n\t ') - elif part_type == 'inline': - markdown, stored_file = process_attachment(content) - stored_files.append(stored_file) - body_markdown += markdown - - #if the response separator is present - - #split the body with it, and discard the "so and so wrote:" part - if reply_code: - signature = extract_user_signature(body_markdown, reply_code) - else: - signature = None - body_markdown = extract_reply(body_markdown) - - body_markdown += attachments_markdown - return body_markdown.strip(), stored_files, signature - - -def process_emailed_question( - from_address, subject, body_text, stored_files, - tags=None, group_id=None -): - """posts question received by email or bounces the message""" - #a bunch of imports here, to avoid potential circular import issues - from askbot.forms import AskByEmailForm - from askbot.models import ReplyAddress, User - from askbot.mail import messages - - reply_to = None - try: - #todo: delete uploaded files when posting by email fails!!! - data = { - 'sender': from_address, - 'subject': subject, - 'body_text': body_text - } - form = AskByEmailForm(data) - if form.is_valid(): - email_address = form.cleaned_data['email'] - user = User.objects.get(email__iexact = email_address) - - if user.can_post_by_email() is False: - raise PermissionDenied(messages.insufficient_reputation(user)) - - body_text = form.cleaned_data['body_text'] - stripped_body_text = user.strip_email_signature(body_text) - signature_not_detected = ( - stripped_body_text == body_text and user.email_signature - ) - - #ask for signature response if user's email has not been - #validated yet or if email signature could not be found - if user.email_isvalid is False or signature_not_detected: - - reply_to = ReplyAddress.objects.create_new( - user = user, - reply_action = 'validate_email' - ).as_email_address() - message = messages.ask_for_signature(user, footer_code = reply_to) - raise PermissionDenied(message) - - tagnames = form.cleaned_data['tagnames'] - title = form.cleaned_data['title'] - - #defect - here we might get "too many tags" issue - if tags: - tagnames += ' ' + ' '.join(tags) - - - user.post_question( - title=title, - tags=tagnames.strip(), - body_text=stripped_body_text, - by_email=True, - email_address=from_address, - group_id=group_id - ) - else: - raise ValidationError() - - except User.DoesNotExist: - bounce_email(email_address, subject, reason = 'unknown_user') - except User.MultipleObjectsReturned: - bounce_email(email_address, subject, reason = 'problem_posting') - except PermissionDenied, error: - bounce_email( - email_address, - subject, - reason = 'permission_denied', - body_text = unicode(error), - reply_to = reply_to - ) - except ValidationError: - if from_address: - bounce_email( - from_address, - subject, - reason = 'problem_posting', - ) |