diff options
Diffstat (limited to 'askbot/models/__init__.py')
-rw-r--r-- | askbot/models/__init__.py | 201 |
1 files changed, 171 insertions, 30 deletions
diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index c1beb594..adf7fb90 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -1,6 +1,14 @@ from askbot import startup_procedures startup_procedures.run() +from django.contrib.auth.models import User +#set up a possibility for the users to follow others +try: + import followit + followit.register(User) +except ImportError: + pass + import collections import datetime import hashlib @@ -11,7 +19,6 @@ from django.db.models import signals as django_signals from django.template import Context from django.utils.translation import ugettext as _ from django.utils.translation import ungettext -from django.contrib.auth.models import User from django.utils.safestring import mark_safe from django.utils.html import escape from django.db import models @@ -26,16 +33,24 @@ from askbot.const.message_keys import get_i18n_message from askbot.conf import settings as askbot_settings from askbot.models.question import Thread from askbot.skins import utils as skin_utils +from askbot.mail import messages from askbot.models.question import QuestionView, AnonymousQuestion +from askbot.models.question import DraftQuestion from askbot.models.question import FavoriteQuestion from askbot.models.tag import Tag, MarkedTag +from askbot.models.tag import format_personal_group_name +from askbot.models.group import get_groups, get_global_group from askbot.models.user import EmailFeedSetting, ActivityAuditStatus, Activity from askbot.models.user import GroupMembership, GroupProfile -from askbot.models.post import Post, PostRevision, PostFlagReason, AnonymousAnswer +from askbot.models.post import Post, PostRevision +from askbot.models.post import PostFlagReason, AnonymousAnswer +from askbot.models.post import PostToGroup +from askbot.models.post import DraftAnswer from askbot.models.reply_by_email import ReplyAddress from askbot.models import signals from askbot.models.badges import award_badges_signal, get_badge, BadgeData from askbot.models.repute import Award, Repute, Vote +from askbot.models.widgets import AskWidget, QuestionWidget from askbot import auth from askbot.utils.decorators import auto_now_timestamp from askbot.utils.slug import slugify @@ -55,16 +70,38 @@ def get_admins_and_moderators(): models.Q(is_superuser=True) | models.Q(status='m') ) -def get_users_by_text_query(search_query): +def get_admin(): + """returns admin with the lowest user ID + if there are no users at all - creates one + with name "admin" and unusable password + otherwise raises User.DoesNotExist + """ + try: + return User.objects.filter( + is_superuser=True + ).order_by('id')[0] + except IndexError: + if User.objects.filter(username='_admin_').count() == 0: + admin = User.objects.create_user('_admin_', '') + admin.set_unusable_password() + admin.set_admin_status() + admin.save() + return admin + else: + raise User.DoesNotExist + +def get_users_by_text_query(search_query, users_query_set = None): """Runs text search in user names and profile. For postgres, search also runs against user group names. """ import askbot + if users_query_set is None: + users_query_set = User.objects.all() if 'postgresql_psycopg2' in askbot.get_database_engine_name(): from askbot.search import postgresql - return postgresql.run_full_text_search(User.objects.all(), search_query) + return postgresql.run_full_text_search(users_query_set, search_query) else: - return User.objects.filter( + return users_query_set.filter( models.Q(username__icontains=search_query) | models.Q(about__icontains=search_query) ) @@ -275,7 +312,7 @@ def user_get_marked_tag_names(self, reason): attr_name = MARKED_TAG_PROPERTY_MAP[reason] wildcard_tags = getattr(self, attr_name).split() tag_names.extend(wildcard_tags) - + return tag_names def user_has_affinity_to_question(self, question = None, affinity_type = None): @@ -333,13 +370,20 @@ def user_has_interesting_wildcard_tags(self): and self.interesting_tags != '' ) +def user_can_create_tags(self): + """true if user can create tags""" + if askbot_settings.ENABLE_TAG_MODERATION: + return self.is_administrator_or_moderator() + else: + return True + def user_can_have_strong_url(self): """True if user's homepage url can be followed by the search engine crawlers""" return (self.reputation >= askbot_settings.MIN_REP_TO_HAVE_STRONG_URL) def user_can_post_by_email(self): - """True, if reply by email is enabled + """True, if reply by email is enabled and user has sufficient reputatiton""" return askbot_settings.REPLY_BY_EMAIL and \ self.reputation > askbot_settings.MIN_REP_TO_POST_BY_EMAIL @@ -1129,6 +1173,8 @@ def user_post_comment( added_at = timestamp, by_email = by_email ) + comment.add_to_groups([self.get_personal_group()]) + parent_post.thread.invalidate_cached_data() award_badges_signal.send( None, @@ -1478,6 +1524,8 @@ def user_post_question( tags = None, wiki = False, is_anonymous = False, + is_private = False, + group_id = None, timestamp = None, by_email = False, email_address = None @@ -1507,6 +1555,8 @@ def user_post_question( added_at = timestamp, wiki = wiki, is_anonymous = is_anonymous, + is_private = is_private, + group_id = group_id, by_email = by_email, email_address = email_address ) @@ -1544,7 +1594,8 @@ def user_edit_post(self, body_text = None, revision_comment = None, timestamp = None, - by_email = False + by_email = False, + is_private = False ): """a simple method that edits post body todo: unify it in the style of just a generic post @@ -1571,7 +1622,8 @@ def user_edit_post(self, body_text = body_text, timestamp = timestamp, revision_comment = revision_comment, - by_email = by_email + by_email = by_email, + is_private = is_private ) elif post.post_type == 'tag_wiki': post.apply_edit( @@ -1596,6 +1648,7 @@ def user_edit_question( tags = None, wiki = False, edit_anonymously = False, + is_private = False, timestamp = None, force = False,#if True - bypass the assert by_email = False @@ -1613,6 +1666,7 @@ def user_edit_question( tags = tags, wiki = wiki, edit_anonymously = edit_anonymously, + is_private = is_private, by_email = by_email ) @@ -1632,6 +1686,7 @@ def user_edit_answer( body_text = None, revision_comment = None, wiki = False, + is_private = False, timestamp = None, force = False,#if True - bypass the assert by_email = False @@ -1644,8 +1699,10 @@ def user_edit_answer( text = body_text, comment = revision_comment, wiki = wiki, + is_private = is_private, by_email = by_email ) + answer.thread.invalidate_cached_data() award_badges_signal.send(None, event = 'edit_answer', @@ -1702,6 +1759,7 @@ def user_post_answer( body_text = None, follow = False, wiki = False, + is_private = False, timestamp = None, by_email = False ): @@ -1767,8 +1825,11 @@ def user_post_answer( added_at = timestamp, email_notify = follow, wiki = wiki, + is_private = is_private, by_email = by_email ) + answer_post.add_to_groups([self.get_personal_group()]) + answer_post.thread.invalidate_cached_data() award_badges_signal.send(None, event = 'post_answer', @@ -2140,6 +2201,37 @@ def get_profile_link(self): return mark_safe(profile_link) +def user_get_groups(self, private=False): + """returns a query set of groups to which user belongs""" + #todo: maybe cache this query + return Tag.group_tags.get_for_user(self, private=private) + +def user_get_personal_group(self): + group_name = format_personal_group_name(self) + return Tag.group_tags.get(name=group_name) + +def user_get_foreign_groups(self): + """returns a query set of groups to which user does not belong""" + #todo: maybe cache this query + user_group_ids = self.get_groups().values_list('id', flat = True) + return get_groups().exclude(id__in = user_group_ids) + +def user_get_primary_group(self): + """a temporary function - returns ether None or + first non-personal non-everyone group + works only for one real private group per-person + """ + groups = self.get_groups(private=True) + for group in groups: + if group.name.startswith('_internal_'): + continue + return group + return None + +def user_can_make_group_private_posts(self): + """simplest implementation: user belongs to at least one group""" + return self.get_groups(private=True).count() > 0 + def user_get_groups_membership_info(self, groups): """returts a defaultdict with values that are dictionaries with the following keys and values: @@ -2166,7 +2258,7 @@ def user_get_groups_membership_info(self, groups): info[group.id]['can_join'] = group.group_profile.can_accept_user(self) return info - + def user_get_karma_summary(self): @@ -2311,7 +2403,7 @@ def _process_vote(user, post, timestamp=None, cancel=False, vote_type=None): auth.onDownVotedCanceled(vote, post, user, timestamp) else: auth.onDownVoted(vote, post, user, timestamp) - + post.thread.invalidate_cached_data() if post.post_type == 'question': @@ -2403,12 +2495,12 @@ def flag_post(user, post, timestamp=None, cancel=False, cancel_all = False, forc content_type = post_content_type, object_id=post.id ) for flag in all_flags: - auth.onUnFlaggedItem(post, flag.user, timestamp=timestamp) + auth.onUnFlaggedItem(post, flag.user, timestamp=timestamp) elif cancel:#todo: can't unflag? if force == False: user.assert_can_remove_flag_offensive(post = post) - auth.onUnFlaggedItem(post, user, timestamp=timestamp) + auth.onUnFlaggedItem(post, user, timestamp=timestamp) else: if force == False: @@ -2533,6 +2625,12 @@ def user_edit_group_membership(self, user = None, group = None, action = None): else: raise ValueError('invalid action') +def user_join_group(self, group): + self.edit_group_membership(group=group, user=self, action='add') + +def user_leave_group(self, group): + self.edit_group_membership(group=group, user=self, action='remove') + def user_is_group_member(self, group = None): return self.group_memberships.filter(group = group).count() == 1 @@ -2559,6 +2657,10 @@ User.add_to_class('get_gravatar_url', user_get_gravatar_url) User.add_to_class('get_or_create_fake_user', user_get_or_create_fake_user) User.add_to_class('get_marked_tags', user_get_marked_tags) User.add_to_class('get_marked_tag_names', user_get_marked_tag_names) +User.add_to_class('get_groups', user_get_groups) +User.add_to_class('get_foreign_groups', user_get_foreign_groups) +User.add_to_class('get_personal_group', user_get_personal_group) +User.add_to_class('get_primary_group', user_get_primary_group) User.add_to_class('strip_email_signature', user_strip_email_signature) User.add_to_class('get_groups_membership_info', user_get_groups_membership_info) User.add_to_class('get_anonymous_name', user_get_anonymous_name) @@ -2601,13 +2703,17 @@ User.add_to_class('unfollow_question', user_unfollow_question) User.add_to_class('is_following_question', user_is_following_question) User.add_to_class('mark_tags', user_mark_tags) User.add_to_class('update_response_counts', user_update_response_counts) +User.add_to_class('can_create_tags', user_can_create_tags) User.add_to_class('can_have_strong_url', user_can_have_strong_url) User.add_to_class('can_post_by_email', user_can_post_by_email) User.add_to_class('can_post_comment', user_can_post_comment) +User.add_to_class('can_make_group_private_posts', user_can_make_group_private_posts) User.add_to_class('is_administrator', user_is_administrator) User.add_to_class('is_administrator_or_moderator', user_is_administrator_or_moderator) User.add_to_class('set_admin_status', user_set_admin_status) User.add_to_class('edit_group_membership', user_edit_group_membership) +User.add_to_class('join_group', user_join_group) +User.add_to_class('leave_group', user_leave_group) User.add_to_class('is_group_member', user_is_group_member) User.add_to_class('remove_admin_status', user_remove_admin_status) User.add_to_class('is_moderator', user_is_moderator) @@ -2719,6 +2825,8 @@ def format_instant_notification_email( assert(isinstance(post, Post) and post.is_question()) elif update_type == 'new_question': assert(isinstance(post, Post) and post.is_question()) + elif update_type == 'post_shared': + pass else: raise ValueError('unexpected update_type %s' % update_type) @@ -2743,23 +2851,25 @@ def format_instant_notification_email( content_preview += '<p>======= Full thread summary =======</p>' - content_preview += post.thread.format_for_email() + content_preview += post.thread.format_for_email(user=to_user) - if post.is_comment(): + if update_type == 'post_shared': + user_action = _('%(user)s shared a %(post_link)s.') + elif post.is_comment(): if update_type.endswith('update'): user_action = _('%(user)s edited a %(post_link)s.') else: - user_action = _('%(user)s posted a %(post_link)s') + user_action = _('%(user)s posted a %(post_link)s') elif post.is_answer(): if update_type.endswith('update'): user_action = _('%(user)s edited an %(post_link)s.') else: - user_action = _('%(user)s posted an %(post_link)s.') + user_action = _('%(user)s posted an %(post_link)s.') elif post.is_question(): if update_type.endswith('update'): user_action = _('%(user)s edited a %(post_link)s.') else: - user_action = _('%(user)s posted a %(post_link)s.') + user_action = _('%(user)s posted a %(post_link)s.') else: raise ValueError('unrecognized post type') @@ -2787,7 +2897,7 @@ def format_instant_notification_email( reply_separator += '</p>' else: reply_separator = user_action - + update_data = { 'update_author_name': from_user.username, 'receiving_user_name': to_user.username, @@ -2816,7 +2926,7 @@ def get_reply_to_addresses(user, post): the first address - always a real email address, the second address is not ``None`` only for "question" posts. - When the user is notified of a new question - + When the user is notified of a new question - i.e. `post` is a "quesiton", he/she will need to choose - whether to give a question or a comment, thus we return the second address - for the comment reply. @@ -2902,7 +3012,7 @@ def send_instant_notifications_about_activity_in_post( update_type = update_type, template = get_template('instant_notification.html') ) - + headers['Reply-To'] = reply_address mail.send_mail( subject_line = subject_line, @@ -2923,7 +3033,7 @@ def notify_author_of_published_revision( if revision.should_notify_author_about_publishing(was_approved): from askbot.tasks import notify_author_of_published_revision_celery_task notify_author_of_published_revision_celery_task.delay(revision) - + #todo: move to utils def calculate_gravatar_hash(instance, **kwargs): @@ -3235,6 +3345,36 @@ def send_respondable_email_validation_message( ) +def add_user_to_global_group(sender, instance, created, **kwargs): + """auto-joins user to the global group + ``instance`` is an instance of ``User`` class + """ + if created: + instance.edit_group_membership( + group=get_global_group(), + user=instance, + action='add' + ) + + +def add_user_to_personal_group(sender, instance, created, **kwargs): + """auto-joins user to his/her personal group + ``instance`` is an instance of ``User`` class + """ + if created: + #todo: groups will indeed need to be separated from tags + #so that we can use less complicated naming scheme + #in theore here we may have two users that will have + #identical group names!!! + group_name = format_personal_group_name(instance) + group = Tag.group_tags.get_or_create( + group_name=group_name, user=instance + ) + instance.edit_group_membership( + group=group, user=instance, action='add' + ) + + def greet_new_user(user, **kwargs): """sends welcome email to the newly created user @@ -3307,7 +3447,7 @@ def update_user_avatar_type_flag(instance, **kwargs): def make_admin_if_first_user(instance, **kwargs): """first user automatically becomes an administrator the function is run only once in the interpreter session - """ + """ import sys #have to check sys.argv to satisfy the test runner #which fails with the cache-based skipping @@ -3326,6 +3466,8 @@ def make_admin_if_first_user(instance, **kwargs): django_signals.pre_save.connect(make_admin_if_first_user, sender=User) django_signals.pre_save.connect(calculate_gravatar_hash, sender=User) django_signals.post_save.connect(add_missing_subscriptions, sender=User) +django_signals.post_save.connect(add_user_to_global_group, sender=User) +django_signals.post_save.connect(add_user_to_personal_group, sender=User) django_signals.post_save.connect(record_award_event, sender=Award) django_signals.post_save.connect(notify_award_message, sender=Award) django_signals.post_save.connect(record_answer_accepted, sender=Post) @@ -3355,12 +3497,6 @@ signals.post_updated.connect(record_post_update_activity) signals.post_revision_published.connect(notify_author_of_published_revision) signals.site_visited.connect(record_user_visit) -#set up a possibility for the users to follow others -try: - import followit - followit.register(User) -except ImportError: - pass __all__ = [ 'signals', @@ -3370,11 +3506,14 @@ __all__ = [ 'QuestionView', 'FavoriteQuestion', 'AnonymousQuestion', + 'DraftQuestion', 'AnonymousAnswer', + 'DraftAnswer', 'Post', 'PostRevision', + 'PostToGroup', 'Tag', 'Vote', @@ -3396,5 +3535,7 @@ __all__ = [ 'ReplyAddress', 'get_model', - 'get_admins_and_moderators' + 'get_admins_and_moderators', + 'get_groups', + 'get_global_group', ] |