def favorite(request): """ Add this item to the user's favorites. Return OK. """ action = request.POST.get('action', 'favorite') asset_id = request.POST.get('asset_id') if not asset_id: raise http.Http404 typepad.client.batch_request() request.typepad_user = get_user(request) asset = models.Asset.get_by_url_id(asset_id) typepad.client.complete_batch() if action == 'favorite': fav = models.Favorite() fav.in_reply_to = asset.asset_ref request.typepad_user.favorites.post(fav) signals.favorite_created.send(sender=favorite, instance=fav, parent=asset, group=request.group) else: typepad.client.batch_request() fav = models.Favorite.get_by_user_asset(request.typepad_user.url_id, asset_id) typepad.client.complete_batch() fav.delete() signals.favorite_deleted.send(sender=favorite, instance=fav, parent=asset, group=request.group) return http.HttpResponse('OK')
def upload_complete(request): """ Callback after uploading directly to TypePad which verifies the response as 'okay' or displays an error message page to the user. """ status = request.GET['status'] if status == '201' or status == '200': # Signal that a new object has been created parts = urlparse(request.GET['asset_url']) instance = models.Asset.get(parts[2], batch=False) request.flash.add('notices', _('Thanks for the %(type)s!') \ % { 'type': instance.type_label.lower() }) signals.asset_created.send(sender=upload_complete, instance=instance, group=request.group) # Redirect to clear the GET data if settings.FEATURED_MEMBER: typepad.client.batch_request() user = get_user(request) typepad.client.complete_batch() if not user.is_authenticated() or not user.is_featured_member: return HttpResponseRedirect(reverse('group_events')) return HttpResponseRedirect(reverse('home')) return render_to_response('motion/error.html', { 'message': request.GET['error'], }, context_instance=RequestContext(request))
def direct_to_template(request, template, extra_context=None, mimetype=None, **kwargs): """ Render a given template with any extra URL parameters in the context as ``{{ params }}``. """ if extra_context is None: extra_context = {} dictionary = {'params': kwargs} for key, value in extra_context.items(): if callable(value): dictionary[key] = value() else: dictionary[key] = value c = RequestContext(request, dictionary) if not hasattr(request, 'typepad_user') or not request.typepad_user: typepad.client.batch_request() user = get_user(request) typepad.client.complete_batch() request.typepad_user = user c.update({ 'user': _PlainUserWarningProxy(request.typepad_user), 'typepad_user': request.typepad_user, 'request': request, }) t = loader.get_template(template) return HttpResponse(t.render(c), mimetype=mimetype)
def crosspost_options(request): """ Add this option to the user's preferred crossposting options. Return OK. """ typepad.client.batch_request() request.typepad_user = get_user(request) typepad.client.complete_batch() # Current crossposting options co = motion.models.CrosspostOptions.get(request.typepad_user.url_id) if co is None: co = motion.models.CrosspostOptions(user_id=request.typepad_user.url_id) if co.crosspost: options = json.loads(co.crosspost) else: options = [] # Get checkbox value and if it is checked or unchecked value = request.POST.get('option_value') if not value: raise http.Http404 checked = request.POST.get('checked') == 'true' # Update crossposting options if checked and not value in options: options.append(value) elif value in options: options.remove(value) co.crosspost = json.dumps(options) co.save() return http.HttpResponse('OK')
def home(request, page=1, **kwargs): """Display the home page view, based on local configuration. The home page may be the list of recent member activity, a featured user's profile page, or the recent activity in the group of people you are following. Which home page is shown depends on the related settings: * If the `FEATURED_MEMBER` setting is set, the home page is the `FeaturedMemberView`. * Otherwise, if `HOME_MEMBER_EVENTS` is ``True`` and the viewer is logged in, the home page is the viewer's `FollowingEventsView`. * Otherwise, the home page is the `GroupEventsView`. By default there's no `FEATURED_MEMBER` and `HOME_MEMBER_EVENTS` is ``False``, so the home page view is the group events view. To always use a certain view instead of these rules, change your urlconf to use a different view for the home page. """ if settings.FEATURED_MEMBER: # Home page is a featured user. return FeaturedMemberView(request, settings.FEATURED_MEMBER, page=page, view='home', **kwargs) if settings.HOME_MEMBER_EVENTS: typepad.client.batch_request() user = get_user(request) typepad.client.complete_batch() if user.is_authenticated(): # Home page is the user's inbox. return FollowingEventsView(request, page=page, view='home', **kwargs) # Home page is group events. return GroupEventsView(request, page=page, view='home', **kwargs)
def browser_upload(request): if not hasattr(request, 'typepad_user'): typepad.client.batch_request() user = get_user(request) typepad.client.complete_batch() request.typepad_user = user if not request.method == 'POST': status = moderation_status(request) if status == Queue.APPROVED: import motion.ajax return motion.ajax.upload_url(request) url = reverse('moderated_upload_url') url = 'for(;;);%s' % url # no third party sites allowed. return HttpResponse(url) if not request.typepad_user.is_authenticated(): return HttpResponseForbidden("invalid request") data = json.loads(request.POST['asset']) tp_asset = typepad.Asset.from_dict(data) moderate_post(request, tp_asset) return HttpResponseRedirect(reverse('home'))
def more_comments(request): """ Fetch more comments for the asset and return the HTML for the additional comments. """ asset_id = request.GET.get('asset_id') offset = int(request.GET.get('offset')) or 1 if not asset_id or not offset: raise http.Http404 # Fetch more comments! typepad.client.batch_request() request.typepad_user = get_user(request) asset = models.Asset.get_by_url_id(asset_id) comments = asset.comments.filter(start_index=offset, max_results=settings.COMMENTS_PER_PAGE) typepad.client.complete_batch() ### Moderation if moderation: id_list = [comment.url_id for comment in comments] if id_list: approved = moderation.Approved.objects.filter(asset_id__in=id_list) approved_ids = [a.asset_id for a in approved] suppressed = moderation.Queue.objects.filter( asset_id__in=id_list, status=moderation.Queue.SUPPRESSED) suppressed_ids = [a.asset_id for a in suppressed] flags = moderation.Flag.objects.filter( tp_asset_id__in=id_list, user_id=request.typepad_user.url_id) flag_ids = [f.tp_asset_id for f in flags] for comment in comments: if comment.url_id in suppressed_ids: comment.suppress = True if comment.url_id in approved_ids: comment.moderation_approved = True if comment.url_id in flag_ids: comment.moderation_flagged = True # Render HTML for comments comment_string = '' for comment in comments: comment_string += render_to_string( 'motion/assets/comment.html', { 'comment': comment, 'view': 'permalink', }, context_instance=RequestContext(request)) # Return HTML return http.HttpResponse(comment_string)
def more_comments(request): """ Fetch more comments for the asset and return the HTML for the additional comments. """ asset_id = request.GET.get('asset_id') offset = int(request.GET.get('offset')) or 1 if not asset_id or not offset: raise http.Http404 # Fetch more comments! typepad.client.batch_request() request.typepad_user = get_user(request) asset = models.Asset.get_by_url_id(asset_id) comments = asset.comments.filter(start_index=offset, max_results=settings.COMMENTS_PER_PAGE) typepad.client.complete_batch() ### Moderation if moderation: id_list = [comment.url_id for comment in comments] if id_list: approved = moderation.Approved.objects.filter(asset_id__in=id_list) approved_ids = [a.asset_id for a in approved] suppressed = moderation.Queue.objects.filter(asset_id__in=id_list, status=moderation.Queue.SUPPRESSED) suppressed_ids = [a.asset_id for a in suppressed] flags = moderation.Flag.objects.filter(tp_asset_id__in=id_list, user_id=request.typepad_user.url_id) flag_ids = [f.tp_asset_id for f in flags] for comment in comments: if comment.url_id in suppressed_ids: comment.suppress = True if comment.url_id in approved_ids: comment.moderation_approved = True if comment.url_id in flag_ids: comment.moderation_flagged = True # Render HTML for comments comment_string = '' for comment in comments: comment_string += render_to_string('motion/assets/comment.html', { 'comment': comment, 'view': 'permalink', }, context_instance=RequestContext(request)) # Return HTML return http.HttpResponse(comment_string)
def select_typepad_user(self, request): """ If a session token is found, returns the authenticated TypePad user, otherwise, returns the Django AnonymousUser. Replaces any authentication middleware. """ from typepadapp.auth import get_user request.typepad_user = get_user(request) self.context.update({ 'user': _PlainUserWarningProxy(request.typepad_user), 'typepad_user': request.typepad_user, 'request': request, })
def edit_profile(request): typepad.client.batch_request() user = get_user(request) typepad.client.complete_batch() profile = user.get_profile() profileform = typepadapp.forms.LocalProfileForm(request.POST, instance=profile) if profileform.is_valid(): profileform.save() return http.HttpResponse(json.dumps({'status': 'success', 'data': 'OK'})) else: errorfields = [k for k, v in profileform.errors.items()] return http.HttpResponse(json.dumps({'status': 'error', 'data': ','.join(errorfields)}))
def edit_profile(request): typepad.client.batch_request() user = get_user(request) typepad.client.complete_batch() profile = user.get_profile() profileform = typepadapp.forms.LocalProfileForm(request.POST, instance=profile) if profileform.is_valid(): profileform.save() return http.HttpResponse( json.dumps({ 'status': 'success', 'data': 'OK' })) else: errorfields = [k for k, v in profileform.errors.items()] return http.HttpResponse( json.dumps({ 'status': 'error', 'data': ','.join(errorfields) }))
def crosspost_options(request): """ Add this option to the user's preferred crossposting options. Return OK. """ typepad.client.batch_request() request.typepad_user = get_user(request) typepad.client.complete_batch() # Current crossposting options co = motion.models.CrosspostOptions.get(request.typepad_user.url_id) if co is None: co = motion.models.CrosspostOptions( user_id=request.typepad_user.url_id) if co.crosspost: options = json.loads(co.crosspost) else: options = [] # Get checkbox value and if it is checked or unchecked value = request.POST.get('option_value') if not value: raise http.Http404 checked = request.POST.get('checked') == 'true' # Update crossposting options if checked and not value in options: options.append(value) elif value in options: options.remove(value) co.crosspost = json.dumps(options) co.save() return http.HttpResponse('OK')
def asset_post(request): """Ajax interface for creating a post or comment.""" post_type = request.POST.get('post_type', None) if post_type is None: raise Exception("post_type is a required parameter") if post_type == 'comment': frm = CommentForm(request.POST) if frm.is_valid(): postid = request.POST.get('parent', None) if postid is None: raise Exception("parent is a required parameter") typepad.client.batch_request() user = get_user(request) asset = models.Asset.get_by_url_id(postid) typepad.client.complete_batch() if not user.is_authenticated(): raise Exception("not authorized") request.typepad_user = user comment = frm.save() comment.in_reply_to = asset.asset_ref ### Moderation if moderation: from moderation import views as mod_view if mod_view.moderate_post(request, comment): html = render_to_string( 'motion/assets/comment.html', { 'comment': comment, 'view': 'permalink', }, context_instance=RequestContext(request)) return http.HttpResponse(json.dumps({ 'status': 'moderated', 'data': 'Your comment is held for moderation.' }), mimetype='application/json') try: asset.comments.post(comment) except Exception, e: return http.HttpResponse(json.dumps({ 'status': 'error', 'data': str(e), }), mimetype='application/json') signals.asset_created.send(sender=asset_post, instance=comment, parent=asset, group=request.group) # render response html = render_to_string('motion/assets/comment.html', { 'comment': comment, 'view': 'permalink', }, context_instance=RequestContext(request)) return http.HttpResponse(json.dumps({ 'status': 'posted', 'data': html, 'xid': comment.xid }), mimetype='application/json') else: errorfields = [k for k, v in frm.errors.items()] return http.HttpResponse(json.dumps({ 'status': 'error', 'data': ','.join(errorfields) }), mimetype='application/json')
def moderation_report(request): asset_id = request.POST['asset-id'] try: reason_code = int(request.POST.get('reason', 0)) except ValueError: reason_code = 0 note = request.POST.get('note', None) return_to = request.POST.get('return_to', reverse('home')) return_to = re.sub('.*?/', '/', return_to) ip = request.META['REMOTE_ADDR'] typepad.client.batch_request() user = get_user(request) asset = typepad.Asset.get_by_url_id(asset_id) try: typepad.client.complete_batch() except typepad.Asset.NotFound: if request.is_ajax(): return HttpResponse(_("The requested post was not found."), mimetype='text/plain') else: return HttpResponse('ERROR', mimetype='text/plain') # TODO: Should we behave differently if the user is an admin? queue = Queue.objects.filter(asset_id=asset_id) if not queue: queue = Queue() queue.asset_id = asset_id queue.summary = unicode(asset) queue.asset_type = asset.type_id queue.user_id = asset.user.url_id queue.user_display_name = asset.user.display_name queue.user_userpic = asset.user.userpic queue.flag_count = 1 queue.status = Queue.FLAGGED queue.last_flagged = datetime.now() else: queue = queue[0] queue.flag_count += 1 approved = Approved.objects.filter(asset_id=asset_id) if len(approved): if request.is_ajax(): return HttpResponse(_("This post has been approved by the site moderator."), mimetype='text/plain') else: request.flash.add('notices', _('This post has been approved by the site moderator.')) return HttpResponseRedirect(return_to) # determine if this report is going to suppress the asset or not. if queue.status != Queue.SUPPRESSED: # count # of flags for this reason and asset: if len(settings.REPORT_OPTIONS[reason_code]) > 1: trigger = settings.REPORT_OPTIONS[reason_code][1] count = Flag.objects.filter(tp_asset_id=asset_id, reason_code=reason_code).count() if count + 1 >= trigger: queue.status = Queue.SUPPRESSED queue.save() # to avoid having to hit typepad for viewing this content, # save a local copy to make moderation as fast as possible # this data is removed once the post is processed. if not queue.content: content = QueueContent() content.data = json.dumps(asset.to_dict()) content.queue = queue content.user_token = 'none' content.ip_addr = '0.0.0.0' content.save() flag = Flag.objects.filter(user_id=user.url_id, queue=queue) if not flag: # lets not allow a single user to repeat a report on the same asset flag = Flag() flag.queue = queue flag.tp_asset_id = asset_id flag.user_id = user.url_id flag.user_display_name = user.display_name if reason_code is not None: flag.reason_code = reason_code if note is not None: flag.note = note flag.ip_addr = ip flag.save() else: flag = flag[0] if reason_code and flag.reason_code != reason_code: flag.reason_code = reason_code flag.ip_addr = ip if note is not None: flag.note = note flag.save() if request.is_ajax(): return HttpResponse('OK', mimetype='text/plain') else: request.flash.add('notices', _('Thank you for your report.')) if queue.status == Queue.SUPPRESSED: return HttpResponseRedirect(reverse('home')) else: return HttpResponseRedirect(return_to)
def asset_post(request): """Ajax interface for creating a post or comment.""" post_type = request.POST.get('post_type', None) if post_type is None: raise Exception("post_type is a required parameter") if post_type == 'comment': frm = CommentForm(request.POST) if frm.is_valid(): postid = request.POST.get('parent', None) if postid is None: raise Exception("parent is a required parameter") typepad.client.batch_request() user = get_user(request) asset = models.Asset.get_by_url_id(postid) typepad.client.complete_batch() if not user.is_authenticated(): raise Exception("not authorized") request.typepad_user = user comment = frm.save() comment.in_reply_to = asset.asset_ref ### Moderation if moderation: from moderation import views as mod_view if mod_view.moderate_post(request, comment): html = render_to_string('motion/assets/comment.html', { 'comment': comment, 'view': 'permalink', }, context_instance=RequestContext(request)) return http.HttpResponse(json.dumps({ 'status': 'moderated', 'data': 'Your comment is held for moderation.'}), mimetype='application/json') try: asset.comments.post(comment) except Exception, e: return http.HttpResponse(json.dumps({ 'status': 'error', 'data': str(e), }), mimetype='application/json') signals.asset_created.send(sender=asset_post, instance=comment, parent=asset, group=request.group) # render response html = render_to_string('motion/assets/comment.html', { 'comment': comment, 'view': 'permalink', }, context_instance=RequestContext(request)) return http.HttpResponse(json.dumps({ 'status': 'posted', 'data': html, 'xid':comment.xid}), mimetype='application/json') else: errorfields = [k for k, v in frm.errors.items()] return http.HttpResponse(json.dumps({'status': 'error', 'data': ','.join(errorfields)}), mimetype='application/json')
def moderate(request): """ Moderation actions for the moderation queue. Approve or delete. Return OK. """ if not hasattr(request, 'typepad_user'): typepad.client.batch_request() request.typepad_user = get_user(request) typepad.client.complete_batch() if not (request.typepad_user.is_authenticated() and \ request.typepad_user.is_superuser): return http.HttpResponseForbidden() res = 'OK' item_ids = request.POST.getlist('item_id') if item_ids is None: raise http.Http404 action = request.POST.get('action', None) success = [] fail = [] ban_list = [] for item_id in item_ids: queue = None user = None if action.endswith('_user'): try: user = Blacklist.objects.get(user_id=item_id) except Blacklist.DoesNotExist: if action == 'approve_user': success.append(item_id) continue else: fail.append(item_id) continue else: try: queue = Queue.objects.get(id=item_id) except: fail.append(item_id) continue if action == 'approve': queue.approve() success.append(item_id) elif action in ('delete', 'ban'): # outright delete it?? or do we have a status for this? if action == 'ban': if queue.user_id not in ban_list: # also ban this user typepad.client.batch_request() user_memberships = User.get_by_url_id(queue.user_id).memberships.filter(by_group=request.group) typepad.client.complete_batch() try: user_membership = user_memberships[0] if user_membership.is_admin(): # cannot ban/unban another admin fail.append(item_id) continue user_membership.block() signals.member_banned.send(sender=moderate, membership=user_membership, group=request.group) ban_list.append(queue.user_id) except IndexError: pass # no membership exists; ignore ban tp_asset = None if queue.asset_id: # we need to remove from typepad typepad.client.batch_request() tp_asset = typepad.Asset.get_by_url_id(queue.asset_id) try: typepad.client.complete_batch() except typepad.Asset.NotFound: # already deleted on TypePad... tp_asset = None if tp_asset: tp_asset.delete() content = queue.content if content is not None: if content.attachment is not None and content.attachment.name: # delete the attachment ourselves; this handles # the case where the file may not actually still be # on disk; we'll just ignore that since we're deleting # the row anyway try: content.attachment.delete() except IOError, ex: # something besides "file couldn't be opened"; reraise if ex.errno != 2: raise ex content.attachment = None content.save() queue.delete() success.append(item_id) if tp_asset is not None: signals.asset_deleted.send(sender=moderate, instance=tp_asset, group=request.group) elif action == 'view': if queue.status in (Queue.MODERATED, Queue.SPAM): content = QueueContent.objects.get(queue=queue) data = json.loads(content.data) # supplement with a fake author member, since this isn't # populated into the POSTed data for pre-moderation/spam assets data['author'] = { 'displayName': queue.user_display_name, 'links': [{ 'rel': 'avatar', 'href': queue.user_userpic, 'width': 50, 'height': 50 }] } tp_asset = typepad.Asset.from_dict(data) tp_asset.published = queue.ts else: typepad.client.batch_request() tp_asset = typepad.Asset.get_by_url_id(queue.asset_id) typepad.client.complete_batch() event = typepad.Event() event.object = tp_asset event.actor = tp_asset.author event.published = tp_asset.published # 'view' of 'permalink' causes the full content to render data = { 'entry': tp_asset, 'event': event, 'view': 'permalink' } res = render_to_string("motion/assets/asset.html", data, context_instance=RequestContext(request)) return http.HttpResponse(res)
def generate_members_csv(request): """CSV file generator for member data.""" # file-like obj for csv writing mfile = StringIO.StringIO() writer = csv.writer(mfile) # label header row labels = ["id", "display name", "email", "joined", "gender", "location", "about me", "homepage", "interests"] if settings.AUTH_PROFILE_MODULE: profile_form = typepadapp.forms.LocalProfileForm() for field in profile_form: labels.append(field.label) writer.writerow(labels) # start the download prompt! yield mfile.getvalue() # fetch typepad api data offset = 1 typepad.client.batch_request() request.typepad_user = get_user(request) kwargs = {"start_index": offset, "member": True, "batch": False} members = request.group.memberships.filter(**kwargs) typepad.client.complete_batch() # verify the user is an admin if request.typepad_user.is_superuser: # convert to user list ids = [member.target.id for member in members] # output csv mfile = get_members_csv(members) yield mfile.getvalue() # wiggle room new_offset = len(ids) - 4 # more pages of members while new_offset > offset: offset = new_offset # fetch typepad api data kwargs["start_index"] = offset more = request.group.memberships.filter(**kwargs) # stop if the result is an empty list if not more.entries: break # add members to list members = [] for m in more: if m.target.id not in ids: # remove dupes members.append(m) ids.append(m.target.id) # output csv mfile = get_members_csv(members) yield mfile.getvalue() new_offset = len(ids) - 4
def asset_meta(request): """An AJAX method for returning metadata about a list of assets, in the context of the authenticated user. This method requires POST and accepts one or more asset_id parameters which should be a valid TypePad Asset XID, prefixed with either 'asset-' or 'comment-' (the prefix informs the type of metadata to supply. Comments cannot be favorited, so there is no need to issue favorite requests for them). The response will be a JSON data structure, mapping the supplied IDs as a key to a dictionary containing "favorite" and "can_delete" members that are assigned a true value. If an asset is neither a favorite or can be deleted by the requesting user, the ID will not be present in the response. An example response would look like this: { "asset-asset_xid1": { "favorite": true }, "comment-asset_xid2": { "can_delete": true } } """ if not hasattr(request, 'typepad_user'): typepad.client.batch_request() request.typepad_user = get_user(request) typepad.client.complete_batch() ids = request.POST.getlist('asset_id') if not ids or not request.typepad_user.is_authenticated(): return http.HttpResponse('{}', mimetype='application/json') user_id = request.typepad_user.url_id admin_user = request.typepad_user.is_superuser favs = [] opts = [] meta = {} typepad.client.batch_request() for id in ids: xid = re.sub(r'^(asset|comment)-', '', id) # deleted comments and assets can leave a 'asset-' or 'comment-' in the request if not len(xid): continue # request favorite status for assets if id.startswith('asset-'): favs.append((id, typepad.Favorite.head_by_user_asset(user_id, xid))) # for non-admins and only if this install permits asset deletion, # request if the user can delete this asset. if not admin_user and settings.ALLOW_USERS_TO_DELETE_POSTS: opts.append((id, typepad.Asset.get_by_url_id(xid).options())) if admin_user: meta[id] = {'can_delete': True} typepad.client.complete_batch() for f in favs: if f[1].found(): if f[0] not in meta: meta[f[0]] = {} meta[f[0]]['favorite'] = True for o in opts: if o[1].status == 200: if o[1].can_delete(): if o[0] not in meta: meta[o[0]] = {} meta[o[0]]['can_delete'] = True return http.HttpResponse(json.dumps(meta), mimetype='application/json')