def pay(self, user, amount): """ Users can make payments for request fees. Upon payment, we create a snail mail task and we set the request to a processing status. Payments are always snail mail, because we need to mail the check to the agency. Since collaborators may make payments, we do not assume the user is the request creator. Returns the communication that was generated. """ # We create the payment communication and a snail mail task for it. payable_to = self.agency.payable_to if self.agency else None text = render_to_string('message/communication/payment.txt', { 'amount': amount, 'payable_to': payable_to, }) comm = self.create_out_communication( from_user=user, text=text, user=user, payment=True, snail=True, amount=amount, # we include the latest pdf here under the assumption # it is the invoice include_latest_pdf=True, ) # We perform some logging and activity generation logger.info('%s has paid %0.2f for request %s', user.username, amount, self.title) utils.new_action(user, 'paid fees', target=self) # We return the communication we generated, in case the caller wants to do anything with it return comm
def _user_follow_up(self, request, foia): """Handle follow ups for non-admins""" has_perm = foia.has_perm(request.user, 'followup') contact_info_form = ContactInfoForm(request.POST, foia=foia) has_contact_perm = (request.user.is_authenticated and request.user.profile.is_advanced()) contact_valid = contact_info_form.is_valid() use_contact_info = ( has_contact_perm and contact_info_form.cleaned_data.get('use_contact_information')) comm_sent = self._new_comm( request, foia, has_perm and (not use_contact_info or contact_valid), 'Your follow up has been sent.', contact_info=contact_info_form.cleaned_data if use_contact_info else None, ) if use_contact_info: foia.add_contact_info_note( request.user, contact_info_form.cleaned_data, ) if comm_sent: new_action(request.user, 'followed up on', target=foia) return redirect(foia.get_absolute_url() + '#')
def _admin_follow_up(request, foia): """Handle follow ups for admins""" form = FOIAAdminFixForm( request.POST, prefix="admin_fix", request=request, foia=foia ) if form.is_valid(): foia.update_address( form.cleaned_data["via"], email=form.cleaned_data["email"], other_emails=form.cleaned_data["other_emails"], fax=form.cleaned_data["fax"], ) snail = form.cleaned_data["via"] == "snail" foia.create_out_communication( from_user=form.cleaned_data["from_user"], text=form.cleaned_data["comm"], user=request.user, snail=snail, subject=form.cleaned_data["subject"], ) messages.success(request, "Your follow up has been sent.") new_action(request.user, "followed up on", target=foia) return _get_redirect(request, foia) else: raise FoiaFormError("admin_fix_form", form)
def _user_follow_up(self, request, foia): """Handle follow ups for non-admins""" has_perm = foia.has_perm(request.user, 'followup') contact_info_form = ContactInfoForm(request.POST, foia=foia, prefix='followup') has_contact_perm = request.user.has_perm('foia.set_info_foiarequest') contact_valid = contact_info_form.is_valid() use_contact_info = ( has_contact_perm and contact_info_form.cleaned_data.get('use_contact_information')) comm_sent = self._new_comm( request, foia, has_perm and (not use_contact_info or contact_valid), 'Your follow up has been sent.', contact_info=contact_info_form.cleaned_data if use_contact_info else None, ) if use_contact_info: foia.add_contact_info_note( request.user, contact_info_form.cleaned_data, ) if comm_sent: new_action(request.user, 'followed up on', target=foia) mixpanel_event( request, 'Follow Up', foia.mixpanel_data({ 'Use Contact Info': use_contact_info, }), ) return redirect(foia.get_absolute_url() + '#')
def test_unidentical_notification(self): """A new notification shoudl not mark any with unidentical language as read.""" first_action = new_action(self.request.agency, "completed", target=self.request) second_action = new_action(self.request.agency, "rejected", target=self.request) third_action = new_action(self.owner, "completed", target=self.request) unread_count = self.owner.notifications.get_unread().count() self.request.notify(first_action) eq_( self.owner.notifications.get_unread().count(), unread_count + 1, "The user should have one unread notification.", ) self.request.notify(second_action) eq_( self.owner.notifications.get_unread().count(), unread_count + 2, "The user should have two unread notifications.", ) self.request.notify(third_action) eq_( self.owner.notifications.get_unread().count(), unread_count + 3, "The user should have three unread notifications.", )
def remove_member(self, request): """Removes a single member from an organization""" organization = self.get_object() try: user_pk = request.POST['member'] user = User.objects.select_related('profile').get(pk=user_pk) except (KeyError, User.DoesNotExist): messages.error(request, 'No member selected to remove.') return # let members remove themselves from the organization, but nobody else removing_self = user == request.user user_is_owner = organization.owner == request.user if removing_self or user_is_owner or request.user.is_staff: if organization.remove_member(user): new_action(request.user, 'removed', action_object=user, target=organization) logging.info('%s %s %s from %s.', request.user, 'removed', user, organization) if removing_self: msg = 'You are no longer a member.' else: msg = 'You removed membership from %s.' % user.first_name messages.success(request, msg) else: messages.error( request, 'You do not have permission to remove this member.') return
def _user_follow_up(request, foia): """Handle follow ups for non-admins""" has_perm = foia.has_perm(request.user, "followup") contact_info_form = ContactInfoForm(request.POST, foia=foia, prefix="followup") has_contact_perm = request.user.has_perm("foia.set_info_foiarequest") contact_valid = contact_info_form.is_valid() use_contact_info = has_contact_perm and contact_info_form.cleaned_data.get( "use_contact_information" ) comm_sent = _new_comm( request, foia, has_perm and (not use_contact_info or contact_valid), "Your follow up has been sent.", contact_info=contact_info_form.cleaned_data if use_contact_info else None, ) if use_contact_info: foia.add_contact_info_note(request.user, contact_info_form.cleaned_data) if comm_sent: new_action(request.user, "followed up on", target=foia) mixpanel_event( request, "Follow Up", foia.mixpanel_data({"Use Contact Info": use_contact_info}), ) return _get_redirect(request, foia)
def _admin_follow_up(self, request, foia): """Handle follow ups for admins""" form = FOIAAdminFixForm( request.POST, prefix='admin_fix', request=request, foia=foia, ) if form.is_valid(): foia.update_address( form.cleaned_data['via'], email=form.cleaned_data['email'], other_emails=form.cleaned_data['other_emails'], fax=form.cleaned_data['fax'], ) snail = form.cleaned_data['via'] == 'snail' foia.create_out_communication( from_user=form.cleaned_data['from_user'], text=form.cleaned_data['comm'], user=request.user, snail=snail, subject=form.cleaned_data['subject'], ) messages.success(request, 'Your follow up has been sent.') new_action(request.user, 'followed up on', target=foia) return redirect(foia.get_absolute_url() + '#') else: self.admin_fix_form = form raise FoiaFormError
def form_valid(self, form): """Saves relationship and sends action before returning URL""" redirection = super(ProjectCrowdfundView, self).form_valid(form) crowdfund = self.object project = self.get_project() relationship = ProjectCrowdfunds.objects.create( project=project, crowdfund=crowdfund ) new_action( self.request.user, "began crowdfunding", action_object=relationship.crowdfund, target=relationship.project, ) crowdfund.send_intro_email(self.request.user) mixpanel_event( self.request, "Launch Project Crowdfund", self._project_mixpanel_properties( project, { "Name": crowdfund.name, "Payment Capped": crowdfund.payment_capped, "Payment Required": float(crowdfund.payment_required), "Date Due": crowdfund.date_due.isoformat(), }, ), ) return redirection
def thanks(request, foia): """Handle submitting a thank you follow up""" success_msg = "Your thank you has been sent." has_perm = foia.has_perm(request.user, "thank") comm_sent = _new_comm(request, foia, has_perm, success_msg, thanks=True) if comm_sent: new_action(request.user, verb="thanked", target=foia.agency) return _get_redirect(request, foia)
def crowdfund_request(request, idx, **kwargs): """Crowdfund a request""" # pylint: disable=unused-argument # select for update locks this request in order to prevent a race condition # allowing multiple crowdfunds to be created for it foia = get_object_or_404( FOIARequest.objects.select_for_update().select_related( "agency__jurisdiction", "composer" ), pk=idx, ) # check for unauthorized access if not foia.has_perm(request.user, "crowdfund"): messages.error(request, "You may not crowdfund this request.") return redirect(foia) if request.method == "POST": # save crowdfund object form = CrowdfundForm(request.POST) if form.is_valid(): crowdfund = form.save() foia.crowdfund = crowdfund foia.save(comment="added a crowdfund") messages.success(request, "Your crowdfund has started, spread the word!") new_action( request.user, "began crowdfunding", action_object=crowdfund, target=foia ) crowdfund.send_intro_email(request.user) mixpanel_event( request, "Launch Request Crowdfund", foia.mixpanel_data( { "Name": crowdfund.name, "Payment Capped": crowdfund.payment_capped, "Payment Required": float(crowdfund.payment_required), "Date Due": crowdfund.date_due.isoformat(), } ), ) return redirect(foia) elif request.method == "GET": # create crowdfund form default_crowdfund_duration = 30 date_due = timezone.now() + timedelta(default_crowdfund_duration) initial = { "name": "Crowdfund Request: %s" % str(foia), "description": "Help cover the request fees needed to free these docs!", "payment_required": foia.get_stripe_amount(), "date_due": date_due, "foia": foia, } form = CrowdfundForm(initial=initial) mixpanel_event(request, "Start Request Crowdfund", foia.mixpanel_data()) return render(request, "forms/foia/crowdfund.html", {"form": form})
def close_crowdfund(self, succeeded=False): """Close the crowdfund and create a new task for it once it reaches its goal.""" self.closed = True self.save() self.crowdfundtask_set.create() verb = "ended" if succeeded: logger.info("Crowdfund %d reached its goal.", self.id) verb = "succeeded" new_action(self, verb)
def create_embargo(request, foia): """Apply an embargo to the FOIA""" if foia.has_perm(request.user, "embargo"): foia.embargo = True foia.save(comment="added embargo") logger.info("%s embargoed %s", request.user, foia) new_action(request.user, "embargoed", target=foia) fine_tune_embargo(request, foia) else: logger.error("%s was forbidden from embargoing %s", request.user, foia) messages.error(request, "You cannot embargo requests.")
def _thank(self, request, foia): """Handle submitting a thank you follow up""" success_msg = 'Your thank you has been sent.' has_perm = foia.has_perm(request.user, 'thank') comm_sent = self._new_comm(request, foia, has_perm, success_msg, thanks=True) if comm_sent: new_action(request.user, verb='thanked', target=foia.agency) return redirect(foia.get_absolute_url() + '#')
def crowdfund_request(request, idx, **kwargs): """Crowdfund a request""" # pylint: disable=unused-argument # select for update locks this request in order to prevent a race condition # allowing multiple crowdfunds to be created for it foia = FOIARequest.objects.select_for_update().get(pk=idx) # check for unauthorized access if not foia.has_perm(request.user, 'crowdfund'): messages.error(request, 'You may not crowdfund this request.') return redirect(foia) if request.method == 'POST': # save crowdfund object form = CrowdfundForm(request.POST) if form.is_valid(): crowdfund = form.save() foia.crowdfund = crowdfund foia.save(comment='added a crowdfund') messages.success( request, 'Your crowdfund has started, spread the word!' ) new_action( request.user, 'began crowdfunding', action_object=crowdfund, target=foia ) crowdfund.send_intro_email(request.user) return redirect(foia) elif request.method == 'GET': # create crowdfund form default_crowdfund_duration = 30 date_due = timezone.now() + timedelta(default_crowdfund_duration) initial = { 'name': u'Crowdfund Request: %s' % unicode(foia), 'description': 'Help cover the request fees needed to free these docs!', 'payment_required': foia.get_stripe_amount(), 'date_due': date_due, 'foia': foia } form = CrowdfundForm(initial=initial) return render( request, 'forms/foia/crowdfund.html', {'form': form}, )
def form_valid(self, form): """Saves relationship and sends action before returning URL""" redirection = super(ProjectCrowdfundView, self).form_valid(form) crowdfund = self.object project = self.get_project() relationship = ProjectCrowdfunds.objects.create(project=project, crowdfund=crowdfund) new_action(self.request.user, 'began crowdfunding', action_object=relationship.crowdfund, target=relationship.project) crowdfund.send_intro_email(self.request.user) return redirection
def _flag(self, request, foia): """Allow a user to notify us of a problem with the request""" form = FOIAFlagForm(request.POST) has_perm = foia.has_perm(request.user, 'flag') if has_perm and form.is_valid(): FlaggedTask.objects.create( user=request.user, foia=foia, text=form.cleaned_data['text'], category=form.cleaned_data['category'], ) messages.success(request, 'Problem succesfully reported') new_action(request.user, 'flagged', target=foia) return redirect(foia.get_absolute_url() + '#')
def flag(request, foia): """Allow a user to notify us of a problem with the request""" form = FOIAFlagForm(request.POST, all_choices=True) if form.is_valid(): FlaggedTask.objects.create( user=request.user if request.user.is_authenticated else None, foia=foia, text=form.cleaned_data["text"], category=form.cleaned_data["category"], ) messages.success(request, "Problem succesfully reported") if request.user.is_authenticated: new_action(request.user, "flagged", target=foia) return _get_redirect(request, foia)
def _appeal(self, request, foia): """Handle submitting an appeal, then create an Appeal from the returned communication. """ form = AppealForm(request.POST) has_perm = foia.has_perm(request.user, 'appeal') contact_info_form = ContactInfoForm( request.POST, foia=foia, prefix='appeal', appeal=True, ) has_contact_perm = request.user.has_perm('foia.set_info_foiarequest') contact_valid = contact_info_form.is_valid() use_contact_info = ( has_contact_perm and contact_info_form.cleaned_data.get('use_contact_information')) if not has_perm: messages.error(request, 'You do not have permission to submit an appeal.') return redirect(foia.get_absolute_url() + '#') if not form.is_valid(): messages.error(request, 'You did not submit an appeal.') return redirect(foia.get_absolute_url() + '#') if foia.attachments_over_size_limit(request.user): messages.error(request, 'Total attachment size must be less than 20MB') return redirect(foia.get_absolute_url() + '#') if use_contact_info and not contact_valid: messages.error(request, 'Invalid contact information') return redirect(foia.get_absolute_url() + '#') communication = foia.appeal( form.cleaned_data['text'], request.user, contact_info=contact_info_form.cleaned_data if use_contact_info else None, ) base_language = form.cleaned_data['base_language'] appeal = Appeal.objects.create(communication=communication) appeal.base_language.set(base_language) new_action(request.user, 'appealed', target=foia) messages.success(request, 'Your appeal has been sent.') if use_contact_info: foia.add_contact_info_note( request.user, contact_info_form.cleaned_data, ) return redirect(foia.get_absolute_url() + '#')
class NotificationFactory(factory.django.DjangoModelFactory): """A factory for creating Notification test objects.""" class Meta: model = Notification user = factory.SubFactory(UserFactory) action = factory.LazyAttribute(lambda obj: new_action(obj.user, 'acted'))
def test_basic(self): """An action only needs an actor and a verb.""" actor = UserFactory() verb = 'acted' action = new_action(actor, verb) ok_(isinstance(action, Action), 'An Action should be returned.') eq_(action.actor, actor) eq_(action.verb, verb)
def setUp(self): agency = AgencyFactory() self.owner = UserFactory() self.follower = UserFactory() self.request = FOIARequestFactory(composer__user=self.owner, agency=agency) follow(self.follower, self.request) self.action = new_action(agency, "completed", target=self.request)
def add_members(self, request): """Grants organization membership to a list of users""" organization = self.get_object() if not organization.is_owned_by( request.user) and not request.user.is_staff: messages.error(request, 'You cannot add members this organization.') return form = AddMembersForm(request.POST) if form.is_valid(): new_members = form.cleaned_data['members'] new_member_count = len(new_members) existing_member_count = organization.members.count() if new_member_count + existing_member_count > organization.max_users: difference = (new_member_count + existing_member_count) - organization.max_users seat = 'seats' if difference > 1 else 'seat' messages.error( request, 'You will need to purchase %d %s.' % (difference, seat)) return if not organization.active: messages.error( request, 'You may not add members to an inactive organization.') return members_added = 0 for member in new_members: try: if organization.add_member(member): new_action(request.user, 'added', action_object=member, target=organization) logging.info('%s %s %s to %s.', request.user, 'added', member, organization) members_added += 1 except AttributeError as exception: messages.error(request, exception) if members_added > 0: members_plural = 'members' if members_added > 1 else 'member' messages.success( request, 'You added %d %s.' % (members_added, members_plural)) return
def create_agency_notifications(self): """Create the notifications for when an agency creates a new comm""" if self.foia and self.foia.agency: action = new_action(self.foia.agency, 'sent a communication', action_object=self, target=self.foia) self.foia.notify(action) if self.foia: self.foia.update(self.anchor())
def test_send_notification(self): """The email should send if there are notifications.""" # generate an action on an actor the user follows agency = AgencyFactory() foia = FOIARequestFactory(agency=agency) action = new_action(agency, "completed", target=foia) notify(self.user, action) # generate the email, which should contain the generated action email = self.digest(user=self.user, interval=self.interval) eq_(email.activity["count"], 1, "There should be activity.") eq_(email.send(), 1, "The email should send.")
def _appeal(self, request, foia): """Handle submitting an appeal, then create an Appeal from the returned communication. """ form = AppealForm(request.POST) has_perm = foia.has_perm(request.user, 'appeal') if not has_perm: messages.error(request, 'You do not have permission to submit an appeal.') return redirect(foia.get_absolute_url() + '#') if not form.is_valid(): messages.error(request, 'You did not submit an appeal.') return redirect(foia.get_absolute_url() + '#') communication = foia.appeal(form.cleaned_data['text'], request.user) base_language = form.cleaned_data['base_language'] appeal = Appeal.objects.create(communication=communication) appeal.base_language.set(base_language) new_action(request.user, 'appealed', target=foia) messages.success(request, 'Your appeal has been sent.') return redirect(foia.get_absolute_url() + '#')
def test_digest_follow_requests(self): """Digests should include information on requests I follow.""" # generate an action on a request the user owns other_user = UserFactory() foia = FOIARequestFactory(composer__user=other_user) agency = AgencyFactory() action = new_action(agency, "rejected", target=foia) notify(self.user, action) # generate the email, which should contain the generated action email = self.digest(user=self.user, interval=self.interval) eq_(email.activity["count"], 1, "There should be activity.") eq_(email.send(), 1, "The email should send.")
def appeal(request, foia): """Handle submitting an appeal, then create an Appeal from the returned communication. """ form = AppealForm(request.POST) has_perm = foia.has_perm(request.user, "appeal") contact_info_form = ContactInfoForm( request.POST, foia=foia, prefix="appeal", appeal=True ) has_contact_perm = request.user.has_perm("foia.set_info_foiarequest") contact_valid = contact_info_form.is_valid() use_contact_info = has_contact_perm and contact_info_form.cleaned_data.get( "use_contact_information" ) if not has_perm: messages.error(request, "You do not have permission to submit an appeal.") return _get_redirect(request, foia) if not form.is_valid(): messages.error(request, "You did not submit an appeal.") return _get_redirect(request, foia) if foia.attachments_over_size_limit(request.user): messages.error(request, "Total attachment size must be less than 20MB") return _get_redirect(request, foia) if use_contact_info and not contact_valid: messages.error(request, "Invalid contact information") return _get_redirect(request, foia) communication = foia.appeal( form.cleaned_data["text"], request.user, contact_info=contact_info_form.cleaned_data if use_contact_info else None, ) base_language = form.cleaned_data["base_language"] appeal_ = Appeal.objects.create(communication=communication) appeal_.base_language.set(base_language) new_action(request.user, "appealed", target=foia) messages.success(request, "Your appeal has been sent.") if use_contact_info: foia.add_contact_info_note(request.user, contact_info_form.cleaned_data) return _get_redirect(request, foia)
def save(self, *args, **kwargs): """Creates an action if question is newly asked""" is_new = True if self.pk is None else False super(Question, self).save(*args, **kwargs) if is_new: action = new_action(self.user, 'asked', target=self) # Notify users who subscribe to new question notifications new_question_subscribers = Profile.objects.filter( new_question_notifications=True) users_to_notify = [ profile.user for profile in new_question_subscribers ] notify(users_to_notify, action)
def save(self, *args, **kwargs): """Creates an action if question is newly asked""" # pylint: disable=signature-differs is_new = self.pk is None super(Question, self).save(*args, **kwargs) if is_new: action = new_action(self.user, "asked", target=self) # Notify users who subscribe to new question notifications new_question_subscribers = Profile.objects.filter( new_question_notifications=True) users_to_notify = [ profile.user for profile in new_question_subscribers ] notify(users_to_notify, action)