def obj_create(self, bundle, request=None, **kwargs): """ Handle POST requests to the resource. If the data validates, create a new Review from bundle data. """ form = ReviewForm(bundle.data) if not form.is_valid(): raise self.form_errors(form) app = self.get_app(bundle.data['app']) # Return 409 if the user has already reviewed this app. if self._meta.queryset.filter(addon=app, user=request.user).exists(): raise ImmediateHttpResponse(response=http.HttpConflict()) # Return 403 if the user is attempting to review their own app: if app.has_author(request.user): raise ImmediateHttpResponse(response=http.HttpForbidden()) # Return 403 if not a free app and the user hasn't purchased it. if app.is_premium() and not app.is_purchased(request.amo_user): raise ImmediateHttpResponse(response=http.HttpForbidden()) bundle.obj = Review.objects.create(**self._review_data(request, app, form)) amo.log(amo.LOG.ADD_REVIEW, app, bundle.obj) log.debug('[Review:%s] Created by user %s ' % (bundle.obj.id, request.user.id)) record_action('new-review', request, {'app-id': app.id}) return bundle
def add(request, addon, template=None): if addon.has_author(request.user): return http.HttpResponseForbidden() form = forms.ReviewForm(request.POST or None) if (request.method == 'POST' and form.is_valid() and not request.POST.get('detailed')): details = _review_details(request, addon, form) review = Review.objects.create(**details) amo.log(amo.LOG.ADD_REVIEW, addon, review) log.debug('New review: %s' % review.id) reply_url = shared_url('reviews.reply', addon, review.id, add_prefix=False) data = {'name': addon.name, 'rating': '%s out of 5 stars' % details['rating'], 'review': details['body'], 'reply_url': absolutify(reply_url)} emails = [a.email for a in addon.authors.all()] send_mail('reviews/emails/add_review.ltxt', u'Mozilla Add-on User Review: %s' % addon.name, emails, Context(data), 'new_review') # Update the ratings and counts for the add-on. addon_review_aggregates.delay(addon.id, using='default') return redirect(shared_url('reviews.list', addon)) return jingo.render(request, template, dict(addon=addon, form=form))
def save(self, log_for_developer=True): u = super(UserEditForm, self).save(commit=False) data = self.cleaned_data photo = data['photo'] if photo: u.picture_type = 'image/png' tmp_destination = u.picture_path + '__unconverted' with storage.open(tmp_destination, 'wb') as fh: for chunk in photo.chunks(): fh.write(chunk) tasks.resize_photo.delay(tmp_destination, u.picture_path, set_modified_on=[u]) if data['password']: u.set_password(data['password']) log_cef('Password Changed', 5, self.request, username=u.username, signature='PASSWORDCHANGED', msg='User changed password') if log_for_developer: amo.log(amo.LOG.CHANGE_PASSWORD) log.info(u'User (%s) changed their password' % u) for (i, n) in email.NOTIFICATIONS_BY_ID.items(): enabled = n.mandatory or (str(i) in data['notifications']) UserNotification.update_or_create(user=u, notification_id=i, update={'enabled': enabled}) log.debug(u'User (%s) updated their profile' % u) u.save() return u
def test_rejected(self): comments = "oh no you di'nt!!" amo.set_user(UserProfile.objects.get(username="******")) amo.log( amo.LOG.REJECT_VERSION, self.webapp, self.webapp.current_version, user_id=999, details={"comments": comments, "reviewtype": "pending"}, ) self.webapp.update(status=amo.STATUS_REJECTED) (self.webapp.versions.latest().all_files[0].update(status=amo.STATUS_DISABLED)) r = self.client.get(self.url) eq_(r.status_code, 200) doc = pq(r.content)("#version-status") eq_(doc(".status-rejected").length, 1) eq_(doc("#rejection").length, 1) eq_(doc("#rejection blockquote").text(), comments) my_reply = "fixed just for u, brah" r = self.client.post(self.url, {"notes": my_reply, "resubmit-app": ""}) self.assertRedirects(r, self.url, 302) webapp = self.get_webapp() eq_(webapp.status, amo.STATUS_PENDING, "Reapplied apps should get marked as pending") eq_( webapp.versions.latest().all_files[0].status, amo.STATUS_PENDING, "Files for reapplied apps should get marked as pending", ) action = amo.LOG.WEBAPP_RESUBMIT assert AppLog.objects.filter(addon=webapp, activity_log__action=action.id).exists(), ( "Didn't find `%s` action in logs." % action.short )
def verify(request, uuid): # Because this will be called at any point in the future, # use guid in the URL. addon = get_object_or_404(Addon, guid=uuid) receipt = request.read() verify = Verify(receipt, request) output = verify(check_purchase=False) # Ensure CORS headers are set. def response(data): response = http.HttpResponse(data) for header, value in get_headers(len(output)): response[header] = value return response # Only reviewers or the developers can use this which is different # from the standard receipt verification. The user is contained in the # receipt. if verify.user_id: try: user = UserProfile.objects.get(pk=verify.user_id) except UserProfile.DoesNotExist: user = None if user and (acl.action_allowed_user(user, 'Apps', 'Review') or addon.has_author(user)): amo.log(amo.LOG.RECEIPT_CHECKED, addon, user=user) return response(output) return response(verify.invalid())
def flag(cls, addon, event, message=None): cls.objects.get_or_create(addon=addon) if message: amo.log(event, addon, addon.current_version, details={'comments': message}) else: amo.log(event, addon, addon.current_version)
def payments(request, addon_id, addon): charity = None if addon.charity_id == amo.FOUNDATION_ORG else addon.charity charity_form = forms.CharityForm(request.POST or None, instance=charity, prefix='charity') contrib_form = forms.ContribForm(request.POST or None, instance=addon, initial=forms.ContribForm.initial(addon)) profile_form = forms.ProfileForm(request.POST or None, instance=addon, required=True) if request.method == 'POST': if contrib_form.is_valid(): addon = contrib_form.save(commit=False) addon.wants_contributions = True valid = _save_charity(addon, contrib_form, charity_form) if not addon.has_full_profile(): valid &= profile_form.is_valid() if valid: profile_form.save() if valid: addon.save() messages.success(request, _('Changes successfully saved.')) amo.log(amo.LOG.EDIT_CONTRIBUTIONS, addon) return redirect('devhub.addons.payments', addon.slug) errors = charity_form.errors or contrib_form.errors or profile_form.errors if errors: messages.error(request, _('There were errors in your submission.')) return jingo.render(request, 'devhub/addons/payments.html', dict(addon=addon, charity_form=charity_form, errors=errors, contrib_form=contrib_form, profile_form=profile_form))
def save(self): v = self.product.versions.latest() v.update(approvalnotes=self.cleaned_data['notes']) amo.log(amo.LOG.EDIT_VERSION, v.addon, v) # Mark app as pending again. self.product.mark_done() return v
def find_abuse_escalations(addon_id, **kw): amo.set_user(get_task_user()) weekago = datetime.date.today() - datetime.timedelta(days=7) for abuse in AbuseReport.recent_high_abuse_reports(1, weekago, addon_id): if EscalationQueue.objects.filter(addon=abuse.addon).exists(): # App is already in the queue, no need to re-add it. # TODO: If not in queue b/c of abuse reports, add an # amo.LOG.ESCALATED_HIGH_ABUSE for reviewers. log.info(u'[addon:%s] High abuse reports, but already escalated' % (abuse.addon,)) continue # We have an abuse report and this add-on isn't currently in the # escalated queue... let's see if it has been detected and dealt with # already. logs = (AppLog.objects.filter( activity_log__action=amo.LOG.ESCALATED_HIGH_ABUSE.id, addon=abuse.addon).order_by('-created')) if logs: abuse_since_log = AbuseReport.recent_high_abuse_reports( 1, logs[0].created, addon_id) # If no abuse reports have happened since the last logged abuse # report, do not add to queue. if not abuse_since_log: log.info(u'[addon:%s] High abuse reports, but none since last ' 'escalation' % abuse.addon) continue # If we haven't bailed out yet, escalate this app. msg = u'High number of abuse reports detected' EscalationQueue.objects.create(addon=abuse.addon) amo.log(amo.LOG.ESCALATED_HIGH_ABUSE, abuse.addon, abuse.addon.current_version, details={'comments': msg}) log.info(u'[addon:%s] %s' % (abuse.addon, msg))
def save(self, log_for_developer=True): u = super(UserEditForm, self).save(commit=False) data = self.cleaned_data photo = data['photo'] if photo: u.picture_type = 'image/png' tmp_destination = u.picture_path + '__unconverted' if not os.path.exists(u.picture_dir): os.makedirs(u.picture_dir) fh = open(tmp_destination, 'w') for chunk in photo.chunks(): fh.write(chunk) fh.close() tasks.resize_photo.delay(tmp_destination, u.picture_path, set_modified_on=[u]) if data['password']: u.set_password(data['password']) if log_for_developer: amo.log(amo.LOG.CHANGE_PASSWORD) log.info(u'User (%s) changed their password' % u) for (i, n) in email.NOTIFICATIONS_BY_ID.items(): enabled = n.mandatory or (str(i) in data['notifications']) UserNotification.update_or_create(user=u, notification_id=i, update={'enabled': enabled}) log.debug(u'User (%s) updated their profile' % u) u.save() return u
def create_note(self, action): """ Permissions default to developers + reviewers + Mozilla contacts. For escalation/comment, exclude the developer from the conversation. """ details = {'comments': self.data['comments'], 'reviewtype': self.review_type} if self.files: details['files'] = [f.id for f in self.files] # Commbadge (the future). perm_overrides = { comm.ESCALATION: {'developer': False}, comm.REVIEWER_COMMENT: {'developer': False}, } note_type = comm.ACTION_MAP(action.id) self.comm_thread, self.comm_note = create_comm_note( self.addon, self.version, self.request.amo_user, self.data['comments'], note_type=note_type, # Ignore switch so we don't have to re-migrate new notes. perms=perm_overrides.get(note_type), no_switch=True, attachments=self.attachment_formset) # ActivityLog (ye olde). amo.log(action, self.addon, self.version, user=self.user.get_profile(), created=datetime.now(), details=details, attachments=self.attachment_formset)
def test_json_failboat(self): a = Addon.objects.get() amo.log(amo.LOG['CREATE_ADDON'], a) entry = ActivityLog.objects.get() entry._arguments = 'failboat?' entry.save() eq_(entry.arguments, None)
def create_note(self, action): """ Permissions default to developers + reviewers + Mozilla contacts. For escalation/comment, exclude the developer from the conversation. """ details = {'comments': self.data['comments'], 'reviewtype': self.review_type} if self.files: details['files'] = [f.id for f in self.files] tested = self.get_tested() # You really should... if tested: self.data['comments'] += '\n\n%s' % tested # Commbadge (the future). note_type = comm.ACTION_MAP(action.id) self.comm_thread, self.comm_note = create_comm_note( self.addon, self.version, self.request.user, self.data['comments'], note_type=note_type, attachments=self.attachment_formset) # ActivityLog (ye olde). amo.log(action, self.addon, self.version, user=self.user, created=datetime.now(), details=details, attachments=self.attachment_formset)
def test_hidden(self): version = Version.objects.create(addon=self.addon) amo.log(amo.LOG.COMMENT_VERSION, self.addon, version) res = self.get_response(addon=self.addon.id) key = RssKey.objects.get() res = self.get_response(privaterss=key.key) assert "<title>Comment on" not in res.content
def manifest_updated(self, manifest, upload): """The manifest has updated, update the version and file. This is intended to be used for hosted apps only, which have only a single version and a single file. """ data = parse_addon(upload, self) version = self.versions.latest() version.update(version=data['version']) path = smart_path(nfd_str(upload.path)) file = version.files.latest() file.filename = file.generate_filename(extension='.webapp') file.size = int(max(1, round(storage.size(path) / 1024, 0))) file.hash = (file.generate_hash(path) if waffle.switch_is_active('file-hash-paranoia') else upload.hash) log.info('Updated file hash to %s' % file.hash) file.save() # Move the uploaded file from the temp location. copy_stored_file(path, os.path.join(version.path_prefix, nfd_str(file.filename))) log.info('[Webapp:%s] Copied updated manifest to %s' % ( self, version.path_prefix)) amo.log(amo.LOG.MANIFEST_UPDATED, self)
def delete_photo(request): request.amo_user.update(picture_type='') delete_photo_task.delay(request.amo_user.picture_path) log.debug(u'User (%s) deleted photo' % request.amo_user) messages.success(request, _('Photo Deleted')) amo.log(amo.LOG.USER_EDITED) return http.HttpResponse()
def test_empty_comment(self): amo.log(amo.LOG.REQUEST_VERSION, self.app, self.version, user=self.user, details={}) call_command('migrate_activity_log') note = CommunicationNote.objects.get() eq_(note.thread.addon, self.app) eq_(note.body, '')
def add(request, addon, template=None): if addon.has_author(request.user): raise PermissionDenied form = forms.ReviewForm(request.POST or None) if (request.method == 'POST' and form.is_valid() and not request.POST.get('detailed')): details = _review_details(request, addon, form) review = Review.objects.create(**details) if 'flag' in form.cleaned_data and form.cleaned_data['flag']: rf = ReviewFlag(review=review, user_id=request.user.id, flag=ReviewFlag.OTHER, note='URLs') rf.save() amo.log(amo.LOG.ADD_REVIEW, addon, review) log.debug('New review: %s' % review.id) reply_url = helpers.url('addons.reviews.reply', addon.slug, review.id, add_prefix=False) data = {'name': addon.name, 'rating': '%s out of 5 stars' % details['rating'], 'review': details['body'], 'reply_url': helpers.absolutify(reply_url)} emails = [a.email for a in addon.authors.all()] send_mail('reviews/emails/add_review.ltxt', u'Mozilla Add-on User Review: %s' % addon.name, emails, Context(data), 'new_review') return redirect(helpers.url('addons.reviews.list', addon.slug)) return render(request, template, dict(addon=addon, form=form))
def save(self, *args, **kw): """Save all form data. This will only create a new license if it's not one of the builtin ones. Keyword arguments **log=True** Set to False if you do not want to log this action for display on the developer dashboard. """ log = kw.pop('log', True) changed = self.changed_data builtin = self.cleaned_data['builtin'] if builtin != License.OTHER: license = License.objects.get(builtin=builtin) else: # Save the custom license: license = super(LicenseForm, self).save(*args, **kw) if self.version: if changed or license != self.version.license: self.version.update(license=license) if log: amo.log(amo.LOG.CHANGE_LICENSE, license, self.version.addon) return license
def account_settings(request): ctx = {} # Don't use `request.amo_user` because it's too cached. user = request.user if user.is_authenticated(): amo_user = user.get_profile() form = forms.UserEditForm(request.POST or None, instance=amo_user) if request.method == 'POST': if form.is_valid(): form.save() messages.success(request, _('Settings Updated.')) amo.log(amo.LOG.USER_EDITED) response = redirect('account.settings') # TODO: Detect when we're changing the user's locale and region # and bust on '/', bust on '/settings' for everything else. bust_fragments(response, '/') return response else: messages.form_errors(request) ctx = {'form': form, 'amouser': amo_user} else: if request.method == 'POST': messages.success(request, _('Settings Updated.')) return jingo.render(request, 'account/settings.html', ctx)
def save(self, commit=False): from .tasks import create_persona_preview_image, save_persona_image # We ignore `commit`, since we need it to be `False` so we can save # the ManyToMany fields on our own. addon = super(NewPersonaForm, self).save(commit=False) addon.status = amo.STATUS_UNREVIEWED addon.type = amo.ADDON_PERSONA addon.save() addon._current_version = Version.objects.create(addon=addon, version='0') addon.save() amo.log(amo.LOG.CREATE_ADDON, addon) log.debug('New persona %r uploaded' % addon) data = self.cleaned_data header = data['header_hash'] footer = data['footer_hash'] header = os.path.join(settings.TMP_PATH, 'persona_header', header) footer = os.path.join(settings.TMP_PATH, 'persona_footer', footer) dst = os.path.join(settings.PERSONAS_PATH, str(addon.id)) # Save header, footer, and preview images. save_persona_image(src=header, dst=dst, img_basename='header.jpg') save_persona_image(src=footer, dst=dst, img_basename='footer.jpg') create_persona_preview_image(src=header, dst=dst, img_basename='preview.jpg', set_modified_on=[addon]) # Save user info. user = self.request.amo_user AddonUser(addon=addon, user=user).save() p = Persona() p.persona_id = 0 p.addon = addon p.header = 'header.jpg' p.footer = 'footer.jpg' if data['accentcolor']: p.accentcolor = data['accentcolor'].lstrip('#') if data['textcolor']: p.textcolor = data['textcolor'].lstrip('#') p.license_id = data['license'] p.submit = datetime.now() p.author = user.name p.display_username = user.username p.save() # Save tags. for t in data['tags']: Tag(tag_text=t).save_tag(addon) # Save categories. tb_c = Category.objects.get(application=amo.THUNDERBIRD.id, name__id=data['category'].name_id) AddonCategory(addon=addon, category=data['category']).save() AddonCategory(addon=addon, category=tb_c).save() return addon
def notify_failed(file_pks, job_pk, data, **kw): log.info("[%s@None] Notifying failed for job %s." % (len(file_pks), job_pk)) job = ValidationJob.objects.get(pk=job_pk) set_user(get_task_user()) for result in ValidationResult.objects.filter(validation_job=job, file__pk__in=file_pks): file = result.file version = file.version addon = version.addon context = get_context(addon, version, job, [result], fileob=file) for author in addon.authors.all(): log.info( u"Emailing %s%s for addon %s, file %s about " "error from bulk validation job %s" % (author.email, " [PREVIEW]" if data["preview_only"] else "", addon.pk, file.pk, job_pk) ) args = (Template(data["subject"]).render(context), Template(data["text"]).render(context)) kwargs = dict(from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[author.email]) if data["preview_only"]: job.preview_failure_mail(*args, **kwargs) else: send_mail(*args, **kwargs) amo.log( amo.LOG.BULK_VALIDATION_EMAILED, addon, version, details={"version": version.version, "file": file.filename, "target": job.target_version.version}, )
def find_abuse_escalations(addon_id, **kw): weekago = datetime.date.today() - datetime.timedelta(days=7) add_to_queue = True for abuse in AbuseReport.recent_high_abuse_reports(1, weekago, addon_id): if EscalationQueue.objects.filter(addon=abuse.addon).exists(): # App is already in the queue, no need to re-add it. task_log.info(u'[app:%s] High abuse reports, but already ' u'escalated' % abuse.addon) add_to_queue = False # We have an abuse report... has it been detected and dealt with? logs = (AppLog.objects.filter( activity_log__action=amo.LOG.ESCALATED_HIGH_ABUSE.id, addon=abuse.addon).order_by('-created')) if logs: abuse_since_log = AbuseReport.recent_high_abuse_reports( 1, logs[0].created, addon_id) # If no abuse reports have happened since the last logged abuse # report, do not add to queue. if not abuse_since_log: task_log.info(u'[app:%s] High abuse reports, but none since ' u'last escalation' % abuse.addon) continue # If we haven't bailed out yet, escalate this app. msg = u'High number of abuse reports detected' if add_to_queue: EscalationQueue.objects.create(addon=abuse.addon) amo.log(amo.LOG.ESCALATED_HIGH_ABUSE, abuse.addon, abuse.addon.current_version, details={'comments': msg}) task_log.info(u'[app:%s] %s' % (abuse.addon, msg))
def reply(request, addon, review_id): is_admin = acl.action_allowed(request, 'Addons', 'Edit') is_author = acl.check_addon_ownership(request, addon, dev=True) if not (is_admin or is_author): raise PermissionDenied review = get_object_or_404(Review.objects, pk=review_id, addon=addon) form = ReviewReplyForm(request.POST or None) if form.is_valid(): d = dict(reply_to=review, addon=addon, defaults=dict(user=request.amo_user)) reply, new = Review.objects.get_or_create(**d) for k, v in _review_details(request, addon, form).items(): setattr(reply, k, v) reply.save() action = 'New' if new else 'Edited' if new: amo.log(amo.LOG.ADD_REVIEW, addon, reply) else: amo.log(amo.LOG.EDIT_REVIEW, addon, reply) log.debug('%s reply to %s: %s' % (action, review_id, reply.id)) messages.success(request, _('Your reply was successfully added.') if new else _('Your reply was successfully updated.')) return http.HttpResponse()
def test_rejected(self): comments = "oh no you di'nt!!" amo.set_user(UserProfile.objects.get(username='******')) amo.log(amo.LOG.REJECT_VERSION, self.webapp, self.webapp.current_version, user_id=999, details={'comments': comments, 'reviewtype': 'pending'}) self.webapp.update(status=amo.STATUS_REJECTED) amo.tests.make_rated(self.webapp) (self.webapp.versions.latest() .all_files[0].update(status=amo.STATUS_DISABLED)) r = self.client.get(self.url) eq_(r.status_code, 200) doc = pq(r.content)('#version-status') eq_(doc('.status-rejected').length, 1) eq_(doc('#rejection').length, 1) eq_(doc('#rejection blockquote').text(), comments) my_reply = 'fixed just for u, brah' r = self.client.post(self.url, {'notes': my_reply, 'resubmit-app': ''}) self.assertRedirects(r, self.url, 302) webapp = self.get_webapp() eq_(webapp.status, amo.STATUS_PENDING, 'Reapplied apps should get marked as pending') eq_(webapp.versions.latest().all_files[0].status, amo.STATUS_PENDING, 'Files for reapplied apps should get marked as pending') action = amo.LOG.WEBAPP_RESUBMIT assert AppLog.objects.filter( addon=webapp, activity_log__action=action.id).exists(), ( "Didn't find `%s` action in logs." % action.short)
def test_user_review_history(self): addon_factory(type=amo.ADDON_PERSONA, status=self.status) reviewer = self.create_and_become_reviewer() res = self.client.get(reverse('reviewers.themes.history')) eq_(res.status_code, 200) doc = pq(res.content) eq_(doc('tbody tr').length, 0) theme = Persona.objects.all()[0] for x in range(3): amo.log(amo.LOG.THEME_REVIEW, theme.addon, user=reviewer, details={'action': rvw.ACTION_APPROVE, 'comment': '', 'reject_reason': ''}) res = self.client.get(reverse('reviewers.themes.history')) eq_(res.status_code, 200) doc = pq(res.content) eq_(doc('tbody tr').length, 3) res = self.client.get(reverse('reviewers.themes.logs')) eq_(res.status_code, 200) doc = pq(res.content) eq_(doc('tbody tr').length, 3 * 2) # Double for comment rows.
def user_activity(request, user_id): """Shows the user activity page for another user.""" user = get_object_or_404(UserProfile, pk=user_id) products, contributions, listing = purchase_list(request, user, None) is_admin = acl.action_allowed(request, "Users", "Edit") collections = Collection.objects.filter(author=user_id) user_items = ActivityLog.objects.for_user(user).exclude(action__in=amo.LOG_HIDE_DEVELOPER) admin_items = ActivityLog.objects.for_user(user).filter(action__in=amo.LOG_HIDE_DEVELOPER) amo.log(amo.LOG.ADMIN_VIEWED_LOG, request.amo_user, user=user) return jingo.render( request, "lookup/user_activity.html", { "pager": products, "account": user, "is_admin": is_admin, "listing_filter": listing, "collections": collections, "contributions": contributions, "single": bool(None), "user_items": user_items, "admin_items": admin_items, "show_link": False, }, )
def save(self): for form in self.forms: if form.cleaned_data: action = int(form.cleaned_data['action']) is_flagged = (form.instance.reviewflag_set.count() > 0) if action != reviews.REVIEW_MODERATE_SKIP: # Delete flags. for flag in form.instance.reviewflag_set.all(): flag.delete() review = form.instance addon = review.addon if action == reviews.REVIEW_MODERATE_DELETE: review_addon = review.addon review_id = review.id review.delete() amo.log(amo.LOG.DELETE_REVIEW, review_addon, review_id, details=dict(title=unicode(review.title), body=unicode(review.body), addon_id=addon.id, addon_title=unicode(addon.name), is_flagged=is_flagged)) elif action == reviews.REVIEW_MODERATE_KEEP: review.editorreview = False review.save() amo.log(amo.LOG.APPROVE_REVIEW, review.addon, review, details=dict(title=unicode(review.title), body=unicode(review.body), addon_id=addon.id, addon_title=unicode(addon.name), is_flagged=is_flagged))
def test_dedupe_date(self): # Test that a log spanning old = amo.log(amo.LOG.APPROVE_VERSION, self.addon, self.version) old.update(created=datetime.today() - timedelta(days=1)) amo.log(amo.LOG.APPROVE_VERSION, self.addon, self.version) dedupe_approvals([self.addon.pk]) eq_(ActivityLog.objects.for_addons(self.addon).count(), 2)
def test_addon_logging_pseudo(self): """ If we are given (Addon, 3615) it should log in the AddonLog as well. """ a = Addon.objects.get() amo.log(amo.LOG.CREATE_ADDON, (Addon, a.id)) eq_(AddonLog.objects.count(), 1)
def _record(request, addon): # TODO(andym): we have an API now, replace this with that. logged = request.user.is_authenticated() premium = addon.is_premium() # Require login for premium. if not logged and premium: return http.HttpResponseRedirect(reverse('users.login')) ctx = {'addon': addon.pk} # Don't generate receipts if we're allowing logged-out install. if logged: is_dev = request.check_ownership(addon, require_owner=False, ignore_disabled=True, admin=False) is_reviewer = acl.check_reviewer(request) if (not addon.is_webapp() or not addon.is_public() and not (is_reviewer or is_dev)): raise http.Http404 if (premium and not addon.has_purchased(request.amo_user) and not is_reviewer and not is_dev): raise PermissionDenied # If you are reviewer, you get a user receipt. Use the reviewer tools # to get a reviewer receipt. App developers still get their special # receipt. install_type = (apps.INSTALL_TYPE_DEVELOPER if is_dev else apps.INSTALL_TYPE_USER) # Log the install. installed, c = Installed.objects.get_or_create(addon=addon, user=request.amo_user, install_type=install_type) # Get download source from GET if it exists, if so get the download # source object if it exists. Then grab a client data object to hook up # with the Installed object. download_source = DownloadSource.objects.filter( name=request.REQUEST.get('src', None)) download_source = download_source[0] if download_source else None try: region = request.REGION.id except AttributeError: region = mkt.regions.WORLDWIDE.id client_data, c = ClientData.objects.get_or_create( download_source=download_source, device_type=request.POST.get('device_type', ''), user_agent=request.META.get('HTTP_USER_AGENT', ''), is_chromeless=request.POST.get('chromeless', False), language=request.LANG, region=region) installed.update(client_data=client_data) error = '' receipt_cef.log(request, addon, 'sign', 'Receipt requested') try: receipt = create_receipt(installed) except SigningError: error = _('There was a problem installing the app.') ctx.update(receipt=receipt, error=error) else: if not addon.is_public() or not addon.is_webapp(): raise http.Http404 amo.log(amo.LOG.INSTALL_ADDON, addon) record_action('install', request, { 'app-domain': addon.domain_from_url(addon.origin, allow_none=True), 'app-id': addon.pk, 'anonymous': request.user.is_anonymous(), }) return ctx
def test_there(self): self.client.login(username=self.reviewer.email, password='******') amo.log(amo.LOG.RECEIPT_CHECKED, self.app, user=self.reviewer) res = self.client.get(self.url) eq_(res.status_code, 200) eq_(json.loads(res.content)['status'], True)
def _review(request, addon, version): if (not settings.ALLOW_SELF_REVIEWS and not acl.action_allowed(request, 'Admin', '%') and addon.has_author(request.amo_user)): messages.warning(request, _('Self-reviews are not allowed.')) return redirect(reverse('reviewers.home')) if (addon.status == amo.STATUS_BLOCKED and not acl.action_allowed(request, 'Apps', 'ReviewEscalated')): messages.warning( request, _('Only senior reviewers can review blocklisted apps.')) return redirect(reverse('reviewers.home')) attachment_formset = CommAttachmentFormSet(data=request.POST or None, files=request.FILES or None, prefix='attachment') form = forms.get_review_form(data=request.POST or None, files=request.FILES or None, request=request, addon=addon, version=version, attachment_formset=attachment_formset) postdata = request.POST if request.method == 'POST' else None all_forms = [form, attachment_formset] if version: features_list = [unicode(f) for f in version.features.to_list()] appfeatures_form = AppFeaturesForm(data=postdata, instance=version.features) all_forms.append(appfeatures_form) else: appfeatures_form = None features_list = None queue_type = form.helper.review_type redirect_url = reverse('reviewers.apps.queue_%s' % queue_type) is_admin = acl.action_allowed(request, 'Addons', 'Edit') if request.method == 'POST' and all(f.is_valid() for f in all_forms): old_types = set(o.id for o in addon.device_types) new_types = set(form.cleaned_data.get('device_override')) old_features = set(features_list) new_features = set( unicode(f) for f in appfeatures_form.instance.to_list()) if form.cleaned_data.get('action') == 'public': if old_types != new_types: # The reviewer overrode the device types. We need to not # publish this app immediately. if addon.make_public == amo.PUBLIC_IMMEDIATELY: addon.update(make_public=amo.PUBLIC_WAIT) # And update the device types to what the reviewer set. AddonDeviceType.objects.filter(addon=addon).delete() for device in form.cleaned_data.get('device_override'): addon.addondevicetype_set.create(device_type=device) # Log that the reviewer changed the device types. added_devices = new_types - old_types removed_devices = old_types - new_types msg = _( u'Device(s) changed by ' 'reviewer: {0}' ).format(', '.join([ _(u'Added {0}').format(unicode(amo.DEVICE_TYPES[d].name)) for d in added_devices ] + [ _(u'Removed {0}').format(unicode(amo.DEVICE_TYPES[d].name)) for d in removed_devices ])) amo.log(amo.LOG.REVIEW_DEVICE_OVERRIDE, addon, addon.current_version, details={'comments': msg}) if old_features != new_features: # The reviewer overrode the requirements. We need to not # publish this app immediately. if addon.make_public == amo.PUBLIC_IMMEDIATELY: addon.update(make_public=amo.PUBLIC_WAIT) appfeatures_form.save(mark_for_rereview=False) # Log that the reviewer changed the minimum requirements. added_features = new_features - old_features removed_features = old_features - new_features fmt = ', '.join( [_(u'Added {0}').format(f) for f in added_features] + [_(u'Removed {0}').format(f) for f in removed_features]) # L10n: {0} is the list of requirements changes. msg = _(u'Requirements changed by reviewer: {0}').format(fmt) amo.log(amo.LOG.REVIEW_FEATURES_OVERRIDE, addon, addon.current_version, details={'comments': msg}) score = form.helper.process() if form.cleaned_data.get('notify'): EditorSubscription.objects.get_or_create(user=request.amo_user, addon=addon) # Success message. if score: score = ReviewerScore.objects.filter(user=request.amo_user)[0] rev_str = amo.REVIEWED_CHOICES[score.note_key] try: rev_str = str(unicode(rev_str)) except UnicodeEncodeError: pass # L10N: {0} is the type of review. {1} is the points they earned. # {2} is the points they now have total. success = _( '"{0}" successfully processed (+{1} points, {2} total).'. format(rev_str, score.score, ReviewerScore.get_total(request.amo_user))) else: success = _('Review successfully processed.') messages.success(request, success) return redirect(redirect_url) canned = AppCannedResponse.objects.all() actions = form.helper.actions.items() try: if not version: raise Version.DoesNotExist show_diff = (addon.versions.exclude(id=version.id).filter( files__isnull=False, created__lt=version.created, files__status=amo.STATUS_PUBLIC).latest()) except Version.DoesNotExist: show_diff = None # The actions we should show a minimal form from. actions_minimal = [k for (k, a) in actions if not a.get('minimal')] # We only allow the user to check/uncheck files for "pending" allow_unchecking_files = form.helper.review_type == "pending" versions = (Version.with_deleted.filter( addon=addon).order_by('-created').transform( Version.transformer_activity).transform(Version.transformer)) product_attrs = { 'product': json.dumps(product_as_dict(request, addon, False, 'reviewer'), cls=JSONEncoder), 'manifest_url': addon.manifest_url, } pager = paginate(request, versions, 10) num_pages = pager.paginator.num_pages count = pager.paginator.count ctx = context(request, version=version, product=addon, pager=pager, num_pages=num_pages, count=count, flags=Review.objects.filter(addon=addon, flag=True), form=form, canned=canned, is_admin=is_admin, status_types=amo.MKT_STATUS_CHOICES, show_diff=show_diff, allow_unchecking_files=allow_unchecking_files, actions=actions, actions_minimal=actions_minimal, tab=queue_type, product_attrs=product_attrs, attachment_formset=attachment_formset, appfeatures_form=appfeatures_form) if features_list is not None: ctx['feature_list'] = features_list return jingo.render(request, 'reviewers/review.html', ctx)
def test_total_few(self): for x in range(0, 5): amo.log(amo.LOG['APPROVE_VERSION'], Addon.objects.get()) result = ActivityLog.objects.total_reviews() eq_(len(result), 1) eq_(result[0]['approval_count'], 5)
def save(self): action = self.cleaned_data['action'] comment = self.cleaned_data.get('comment') reject_reason = self.cleaned_data.get('reject_reason') theme = self.cleaned_data['theme'] is_rereview = (theme.rereviewqueuetheme_set.exists() and theme.addon.status not in (amo.STATUS_PENDING, amo.STATUS_REVIEW_PENDING)) theme_lock = ThemeLock.objects.get(theme=self.cleaned_data['theme']) mail_and_log = True if action == rvw.ACTION_APPROVE: if is_rereview: approve_rereview(theme) theme.addon.update(status=amo.STATUS_PUBLIC) theme.approve = datetime.datetime.now() theme.save() elif action == rvw.ACTION_REJECT: if is_rereview: reject_rereview(theme) else: theme.addon.update(status=amo.STATUS_REJECTED) elif action == rvw.ACTION_DUPLICATE: if is_rereview: reject_rereview(theme) else: theme.addon.update(status=amo.STATUS_REJECTED) elif action == rvw.ACTION_FLAG: if is_rereview: mail_and_log = False else: theme.addon.update(status=amo.STATUS_REVIEW_PENDING) elif action == rvw.ACTION_MOREINFO: if not is_rereview: theme.addon.update(status=amo.STATUS_REVIEW_PENDING) if mail_and_log: send_mail(self.cleaned_data, theme_lock) # Log. amo.log(amo.LOG.THEME_REVIEW, theme.addon, details={ 'theme': theme.addon.name.localized_string, 'action': action, 'reject_reason': reject_reason, 'comment': comment }, user=theme_lock.reviewer) log.info('%sTheme %s (%s) - %s' % ('[Rereview] ' if is_rereview else '', theme.addon.name, theme.id, action)) score = 0 if action not in [rvw.ACTION_MOREINFO, rvw.ACTION_FLAG]: score = ReviewerScore.award_points(theme_lock.reviewer, theme.addon, theme.addon.status) theme_lock.delete() return score
def _review(request, addon, version): if (not settings.ALLOW_SELF_REVIEWS and not acl.action_allowed(request, 'Admin', '%') and addon.has_author(request.amo_user)): messages.warning(request, _('Self-reviews are not allowed.')) return redirect(reverse('reviewers.home')) if (addon.status == amo.STATUS_BLOCKED and not acl.action_allowed(request, 'Apps', 'ReviewEscalated')): messages.warning( request, _('Only senior reviewers can review blocklisted apps.')) return redirect(reverse('reviewers.home')) attachment_formset = forms.AttachmentFormSet(data=request.POST or None, files=request.FILES or None, prefix='attachment') form = forms.get_review_form(data=request.POST or None, files=request.FILES or None, request=request, addon=addon, version=version, attachment_formset=attachment_formset) queue_type = form.helper.review_type redirect_url = reverse('reviewers.apps.queue_%s' % queue_type) is_admin = acl.action_allowed(request, 'Addons', 'Edit') forms_valid = lambda: form.is_valid() and attachment_formset.is_valid() if request.method == 'POST' and forms_valid(): old_types = set(o.id for o in addon.device_types) new_types = set(form.cleaned_data.get('device_override')) if (form.cleaned_data.get('action') == 'public' and old_types != new_types): # The reviewer overrode the device types. We need to not publish # this app immediately. if addon.make_public == amo.PUBLIC_IMMEDIATELY: addon.update(make_public=amo.PUBLIC_WAIT) # And update the device types to what the reviewer set. AddonDeviceType.objects.filter(addon=addon).delete() for device in form.cleaned_data.get('device_override'): addon.addondevicetype_set.create(device_type=device) # Log that the reviewer changed the device types. added_devices = new_types - old_types removed_devices = old_types - new_types msg = _(u'Device(s) changed by reviewer: {0}').format(', '.join([ _(u'Added {0}').format(unicode(amo.DEVICE_TYPES[d].name)) for d in added_devices ] + [ _(u'Removed {0}').format(unicode(amo.DEVICE_TYPES[d].name)) for d in removed_devices ])) amo.log(amo.LOG.REVIEW_DEVICE_OVERRIDE, addon, addon.current_version, details={'comments': msg}) form.helper.process() if form.cleaned_data.get('notify'): EditorSubscription.objects.get_or_create(user=request.amo_user, addon=addon) messages.success(request, _('Review successfully processed.')) return redirect(redirect_url) canned = AppCannedResponse.objects.all() actions = form.helper.actions.items() try: show_diff = (addon.versions.exclude(id=version.id).filter( files__isnull=False, created__lt=version.created, files__status=amo.STATUS_PUBLIC).latest()) except Version.DoesNotExist: show_diff = None # The actions we should show a minimal form from. actions_minimal = [k for (k, a) in actions if not a.get('minimal')] # We only allow the user to check/uncheck files for "pending" allow_unchecking_files = form.helper.review_type == "pending" versions = (Version.with_deleted.filter( addon=addon).order_by('-created').transform( Version.transformer_activity).transform(Version.transformer)) product_attrs = { 'product': json.dumps(product_as_dict(request, addon, False, 'reviewer'), cls=JSONEncoder), 'manifest_url': addon.manifest_url, } pager = paginate(request, versions, 10) num_pages = pager.paginator.num_pages count = pager.paginator.count ctx = context(version=version, product=addon, pager=pager, num_pages=num_pages, count=count, flags=Review.objects.filter(addon=addon, flag=True), form=form, canned=canned, is_admin=is_admin, status_types=amo.STATUS_CHOICES, show_diff=show_diff, allow_unchecking_files=allow_unchecking_files, actions=actions, actions_minimal=actions_minimal, tab=queue_type, product_attrs=product_attrs, attachment_formset=attachment_formset) return jingo.render(request, 'reviewers/review.html', ctx)
def remove_addon(self, addon): CollectionAddon.objects.filter(addon=addon, collection=self).delete() if self.listed: amo.log(amo.LOG.REMOVE_FROM_COLLECTION, addon, self) self.save() # To invalidate Collection.
def add_addon(self, addon): "Adds an addon to the collection." CollectionAddon.objects.get_or_create(addon=addon, collection=self) if self.listed: amo.log(amo.LOG.ADD_TO_COLLECTION, addon, self) self.save() # To invalidate Collection.
def test_log_not_admin(self): amo.log(amo.LOG['EDIT_VERSION'], Addon.objects.get()) eq_(len(ActivityLog.objects.admin_events()), 0) eq_(len(ActivityLog.objects.for_developer()), 1)
def test_log_admin(self): amo.log(amo.LOG['OBJECT_EDITED'], Addon.objects.get()) eq_(len(ActivityLog.objects.admin_events()), 1) eq_(len(ActivityLog.objects.for_developer()), 0)
def payment(request, status=None): # Note this is not post required, because PayPal does not reply with a # POST but a GET, that's a sad face. pre, created = (PreApprovalUser.objects.safer_get_or_create( user=request.amo_user)) context = { 'preapproval': pre, 'currency': CurrencyForm(initial={'currency': pre.currency or 'USD'}) } if status: data = request.session.get('setup-preapproval', {}) context['status'] = status if status == 'complete': # The user has completed the setup at PayPal and bounced back. if 'setup-preapproval' in request.session: if waffle.flag_is_active(request, 'solitude-payments'): client.put_preapproval(data={'uuid': request.amo_user}, pk=data['solitude-key']) paypal_log.info(u'Preapproval key created: %s' % request.amo_user.pk) amo.log(amo.LOG.PREAPPROVAL_ADDED) # TODO(solitude): once this is turned off, we will want to # keep preapproval table populated with something, perhaps # a boolean inplace of pre-approval key. pre.update(paypal_key=data.get('key'), paypal_expiry=data.get('expiry')) # If there is a target, bounce to it and don't show a message # we'll let whatever set this up worry about that. if data.get('complete'): return http.HttpResponseRedirect(data['complete']) messages.success( request, _("You're all set for instant app purchases with PayPal.")) del request.session['setup-preapproval'] elif status == 'cancel': # The user has chosen to cancel out of PayPal. Nothing really # to do here, PayPal just bounce to the cancel page if defined. if data.get('cancel'): return http.HttpResponseRedirect(data['cancel']) messages.success( request, _('Your payment pre-approval has been cancelled.')) elif status == 'remove': # The user has an pre approval key set and chooses to remove it if waffle.flag_is_active(request, 'solitude-payments'): other = client.lookup_buyer_paypal(request.amo_user) if other: client.patch_buyer_paypal(pk=other['resource_pk'], data={'key': ''}) if pre.paypal_key: # TODO(solitude): again, we'll want to maintain some local # state in zamboni, so this will probably change to a # boolean in the future. pre.update(paypal_key='') amo.log(amo.LOG.PREAPPROVAL_REMOVED) messages.success( request, _('Your payment pre-approval has been disabled.')) paypal_log.info(u'Preapproval key removed for user: %s' % request.amo_user) return jingo.render(request, 'account/payment.html', context)
def disable(request, addon_id, addon): addon.update(disabled_by_user=True) amo.log(amo.LOG.USER_DISABLE, addon) return redirect(addon.get_dev_url('versions'))
def process_iarc_changes(date=None): """ Queries IARC for recent changes in the past 24 hours (or date provided). If date provided use it. It should be in the form YYYY-MM-DD. """ if not date: date = datetime.date.today() else: date = datetime.datetime.strptime(date, '%Y-%m-%d').date() client = lib.iarc.client.get_iarc_client('services') xml = lib.iarc.utils.render_xml('get_rating_changes.xml', { 'date_from': date - datetime.timedelta(days=1), 'date_to': date, }) resp = client.Get_Rating_Changes(XMLString=xml) data = lib.iarc.utils.IARC_XML_Parser().parse_string(resp) for row in data.get('rows', []): iarc_id = row.get('submission_id') if not iarc_id: log.debug('IARC changes contained no submission ID: %s' % row) continue try: app = Webapp.objects.get(iarc_info__submission_id=iarc_id) except Webapp.DoesNotExist: log.debug('Could not find app associated with IARC submission ID: ' '%s' % iarc_id) continue try: # Any exceptions we catch, log, and keep going. # Process 'new_rating'. ratings_body = row.get('rating_system') rating = RATINGS_MAPPING[ratings_body].get(row['new_rating']) _flag_rereview_adult(app, ratings_body, rating) # Process 'new_descriptors'. native_descs = filter(None, [ s.strip() for s in row.get('new_descriptors', '').split(',')]) descriptors = filter(None, [DESC_MAPPING[ratings_body].get(desc) for desc in native_descs]) app.set_descriptors(descriptors) # Process 'new_interactiveelements'. native_interactives = filter(None, [ s.strip() for s in row.get('new_interactiveelements', '').split(',')]) interactives = filter(None, [INTERACTIVES_MAPPING.get(desc) for desc in native_interactives]) app.set_interactives(interactives) # Save new rating. app.set_content_ratings({ratings_body: rating}) # Log change reason. reason = row.get('change_reason') amo.log(amo.LOG.CONTENT_RATING_CHANGED, app, details={'comments': '%s:%s, %s' % (ratings_body.name, rating.name, reason)}) except Exception as e: log.debug('Exception: %s' % e) continue
def test_review_last_month(self): log = amo.log(amo.LOG['APPROVE_VERSION'], Addon.objects.get()) log.update(created=self.lm) eq_(len(ActivityLog.objects.monthly_reviews()), 0)
def delete(self): log.info(u'Version deleted: %r (%s)' % (self, self.id)) amo.log(amo.LOG.DELETE_VERSION, self.addon, str(self.version)) super(Version, self).delete()
def test_not_total(self): amo.log(amo.LOG['EDIT_VERSION'], Addon.objects.get()) eq_(len(ActivityLog.objects.total_reviews()), 0)
def test_not_review_count(self): amo.log(amo.LOG['EDIT_VERSION'], Addon.objects.get()) eq_(len(ActivityLog.objects.monthly_reviews()), 0)
def test_review_count(self): amo.log(amo.LOG['APPROVE_VERSION'], Addon.objects.get()) result = ActivityLog.objects.monthly_reviews() eq_(len(result), 1) eq_(result[0]['approval_count'], 1) eq_(result[0]['user'], self.user.pk)
def destroy(self, request, *args, **kwargs): obj = self.get_object() amo.log(amo.LOG.DELETE_REVIEW, obj.addon, obj) log.debug('[Review:%s] Deleted by %s' % (obj.pk, self.request.amo_user.id)) return super(RatingViewSet, self).destroy(request, *args, **kwargs)
def delete(self): amo.log(amo.LOG.DELETE_VERSION, self.addon, str(self.version)) super(Version, self).delete()
'user': request.amo_user, 'contribution': contribution, 'refund_url': contribution.get_absolute_refund_url(), 'refund_reason': reason, 'request': request} log.info('Refund request sent by user: %s for addon: %s' % (request.amo_user.pk, addon.pk)) # L10n: %s is the app name. support_mail(_(u'New Refund Request for %s' % addon.name), wizard.tpl('emails/refund-request.txt'), context, settings.NOBODY_EMAIL, [smart_str(addon.support_email)]) # Add this refund request to the queue. contribution.enqueue_refund(amo.REFUND_PENDING, reason) amo.log(amo.LOG.REFUND_REQUESTED, addon) return redirect(reverse('support', args=[contribution.pk, 'refund-sent'])) return wizard.render(request, wizard.tpl('refund.html'), {'product': addon, 'contribution': contribution, 'form': form, 'title': _('Request Refund')}) class SupportWizard(Wizard): title = _lazy('Support') steps = SortedDict([('start', plain), ('site', plain), ('resources', support_resources), ('mozilla', support_mozilla), ('mozilla-sent', plain),
def notify_compatibility_chunk(users, job, data, **kw): log.info('[%s@%s] Sending notification mail for job %s.' % (len(users), notify_compatibility.rate_limit, job.pk)) set_user(get_task_user()) dry_run = data['preview_only'] app_id = job.target_version.application stats = collections.defaultdict(int) stats['processed'] = 0 stats['is_dry_run'] = int(dry_run) for user in users: stats['processed'] += 1 try: for a in chain(user.passing_addons, user.failing_addons): try: results = job.result_set.filter(file__version__addon=a) a.links = [ absolutify( reverse('devhub.bulk_compat_result', args=[a.slug, r.pk])) for r in results ] v = a.current_version or a.latest_version a.compat_link = absolutify( reverse('devhub.versions.edit', args=[a.pk, v.pk])) except: task_error = sys.exc_info() log.error( u'Bulk validation email error for user %s, ' u'addon %s: %s: %s' % (user.email, a.slug, task_error[0], task_error[1]), exc_info=False) context = Context({ 'APPLICATION': unicode(amo.APP_IDS[job.application].pretty), 'VERSION': job.target_version.version, 'PASSING_ADDONS': user.passing_addons, 'FAILING_ADDONS': user.failing_addons, }) log.info( u'Emailing %s%s for %d addons about ' 'bulk validation job %s' % (user.email, ' [PREVIEW]' if dry_run else '', len(user.passing_addons) + len(user.failing_addons), job.pk)) args = (Template(data['subject']).render(context), Template(data['text']).render(context)) kwargs = dict(from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[user.email]) if dry_run: job.preview_notify_mail(*args, **kwargs) else: stats['author_emailed'] += 1 send_mail(*args, **kwargs) amo.log(amo.LOG.BULK_VALIDATION_USER_EMAILED, user, details={ 'passing': [a.id for a in user.passing_addons], 'failing': [a.id for a in user.failing_addons], 'target': job.target_version.version, 'application': app_id }) except: task_error = sys.exc_info() log.error(u'Bulk validation email error for user %s: %s: %s' % (user.email, task_error[0], task_error[1]), exc_info=False) log.info('[%s@%s] bulk email stats for job %s: {%s}' % (len(users), notify_compatibility.rate_limit, job.pk, ', '.join( '%s: %s' % (k, stats[k]) for k in sorted(stats.keys()))))
def issue_refund(request, addon_id, addon, webapp=False): txn_id = request.REQUEST.get('transaction_id') if not txn_id: raise http.Http404 form_enabled = True contribution = get_object_or_404( Contribution, transaction_id=txn_id, type__in=[amo.CONTRIB_PURCHASE, amo.CONTRIB_INAPP]) if (hasattr(contribution, 'refund') and contribution.refund.status not in (amo.REFUND_PENDING, amo.REFUND_FAILED)): # If it's not pending, we've already taken action. messages.error(request, _('Refund already processed.')) form_enabled = False elif request.method == 'POST': if 'issue' in request.POST: try: results = paypal.refund(contribution.paykey) except PaypalError, e: contribution.record_failed_refund(e) paypal_log.error('Refund failed for: %s' % txn_id, exc_info=True) messages.error(request, _('There was an error with ' 'the refund.')) return redirect(addon.get_dev_url('refunds')) for res in results: if res['refundStatus'] == 'ALREADY_REVERSED_OR_REFUNDED': paypal_log.debug( 'Refund attempt for already-refunded paykey: %s, %s' % (contribution.paykey, res['receiver.email'])) messages.error( request, _('Refund was previously issued; ' 'no action taken.')) return redirect(addon.get_dev_url('refunds')) elif res['refundStatus'] == 'NO_API_ACCESS_TO_RECEIVER': paypal_log.debug( 'Refund attempt for product %s with no ' 'refund token: %s, %s' % (contribution.addon.pk, contribution.paykey, res['receiver.email'])) messages.error( request, _("A refund can't be issued at this time. We've " "notified an admin; please try again later.")) return redirect(addon.get_dev_url('refunds')) contribution.mail_approved() amo.log(amo.LOG.REFUND_GRANTED, addon, contribution.user) refund = contribution.enqueue_refund(amo.REFUND_APPROVED) paypal_log.info('Refund %r issued for contribution %r' % (refund.pk, contribution.pk)) messages.success(request, _('Refund issued.')) else: contribution.mail_declined() amo.log(amo.LOG.REFUND_DECLINED, addon, contribution.user) # TODO: Consider requiring a rejection reason for declined refunds. refund = contribution.enqueue_refund(amo.REFUND_DECLINED) paypal_log.info('Refund %r declined for contribution %r' % (refund.pk, contribution.pk)) messages.success(request, _('Refund declined.')) return redirect(addon.get_dev_url('refunds'))
def add(request, addon): if addon.has_author(request.user): # Don't let app owners review their own apps. raise PermissionDenied # Get user agent of user submitting review. If there is an install with # logged user agent that matches the current user agent, hook up that # install's client data with the rating. If there aren't any install that # match, use the most recent install. This implies that user must have an # install to submit a review, but not sure if that logic is worked in, so # default client_data to None. client_data = None user_agent = request.META.get('HTTP_USER_AGENT', '') install = (Installed.objects.filter(user=request.user, addon=addon).order_by('-created')) install_w_user_agent = (install.filter( client_data__user_agent=user_agent).order_by('-created')) has_review = False try: if install_w_user_agent: client_data = install_w_user_agent[0].client_data elif install: client_data = install[0].client_data except ClientData.DoesNotExist: client_data = None data = request.POST or None # Try to get an existing review of the app by this user if we can. try: existing_review = Review.objects.valid().filter(addon=addon, user=request.user)[0] except IndexError: # If one doesn't exist, set it to None. existing_review = None # If the user is posting back, try to process the submission. if data: form = ReviewForm(data) if form.is_valid(): cleaned = form.cleaned_data if existing_review: # If there's a review to overwrite, overwrite it. if (cleaned['body'] != existing_review.body or cleaned['rating'] != existing_review.rating): existing_review.body = cleaned['body'] existing_review.rating = cleaned['rating'] ip = request.META.get('REMOTE_ADDR', '') existing_review.ip_address = ip if 'flag' in cleaned and cleaned['flag']: existing_review.flag = True existing_review.editorreview = True rf = ReviewFlag(review=existing_review, user_id=request.user.id, flag=ReviewFlag.OTHER, note='URLs') rf.save() existing_review.save() # Update ratings and review counts. addon_review_aggregates.delay(addon.id, using='default') amo.log(amo.LOG.EDIT_REVIEW, addon, existing_review) log.debug('[Review:%s] Edited by %s' % (existing_review.id, request.user.id)) messages.success(request, _('Your review was updated successfully!')) # If there is a developer reply to the review, delete it. We do # this per bug 777059. try: reply = existing_review.replies.all()[0] except IndexError: pass else: log.debug('[Review:%s] Deleted reply to %s' % (reply.id, existing_review.id)) reply.delete() else: # If there isn't a review to overwrite, create a new review. review = Review.objects.create(client_data=client_data, **_review_details( request, addon, form)) if 'flag' in cleaned and cleaned['flag']: rf = ReviewFlag(review=review, user_id=request.user.id, flag=ReviewFlag.OTHER, note='URLs') rf.save() amo.log(amo.LOG.ADD_REVIEW, addon, review) log.debug('[Review:%s] Created by user %s ' % (review.id, request.user.id)) messages.success(request, _('Your review was successfully added!')) Addon.objects.invalidate(*[addon]) return redirect(addon.get_ratings_url('list')) # If the form isn't valid, we've set `form` so that it can be used when # the template is rendered below. elif existing_review: # If the user isn't posting back but has an existing review, populate # the form with their existing review and rating. form = ReviewForm({ 'rating': existing_review.rating or 1, 'body': existing_review.body }) has_review = True else: # If the user isn't posting back and doesn't have an existing review, # just show a blank version of the form. form = ReviewForm() # Get app's support url, either from support flow if contribution exists or # author's support url. support_email = str(addon.support_email) if addon.support_email else None try: contrib_id = (Contribution.objects.filter( user=request.user, addon=addon, type__in=(amo.CONTRIB_PURCHASE, amo.CONTRIB_INAPP, amo.CONTRIB_REFUND)).order_by('-created')[0].id) support_url = reverse('support', args=[contrib_id]) except IndexError: support_url = addon.support_url return jingo.render( request, 'ratings/add.html', { 'product': addon, 'form': form, 'support_url': support_url, 'has_review': has_review, 'support_email': support_email })
def log_review(self, num): r = Review(addon=self.addon) for i in xrange(num): amo.log(amo.LOG.ADD_REVIEW, self.addon, r)
def addons_section(request, addon_id, addon, section, editable=False, webapp=False): basic = AppFormBasic if webapp else addon_forms.AddonFormBasic models = { 'basic': basic, 'media': AppFormMedia, 'details': AppFormDetails, 'support': AppFormSupport, 'technical': addon_forms.AddonFormTechnical, 'admin': forms.AdminSettingsForm } if section not in models: raise http.Http404() tags = previews = restricted_tags = [] cat_form = device_type_form = None if section == 'basic': tags = addon.tags.not_blacklisted().values_list('tag_text', flat=True) cat_form = addon_forms.CategoryFormSet(request.POST or None, addon=addon, request=request) restricted_tags = addon.tags.filter(restricted=True) device_type_form = addon_forms.DeviceTypeForm(request.POST or None, addon=addon) elif section == 'media': previews = PreviewFormSet(request.POST or None, prefix='files', queryset=addon.get_previews()) elif (section == 'admin' and not acl.action_allowed(request, 'Apps', 'Configure') and not acl.action_allowed(request, 'Apps', 'ViewConfiguration')): return http.HttpResponseForbidden() # Get the slug before the form alters it to the form data. valid_slug = addon.app_slug if editable: if request.method == 'POST': if (section == 'admin' and not acl.action_allowed(request, 'Apps', 'Configure')): return http.HttpResponseForbidden() form = models[section](request.POST, request.FILES, instance=addon, request=request) if form.is_valid() and (not previews or previews.is_valid()): addon = form.save(addon) if 'manifest_url' in form.changed_data: addon.update( app_domain=addon.domain_from_url(addon.manifest_url)) update_manifests([addon.pk]) if previews: for preview in previews.forms: preview.save(addon) editable = False if section == 'media': amo.log(amo.LOG.CHANGE_ICON, addon) else: amo.log(amo.LOG.EDIT_PROPERTIES, addon) valid_slug = addon.app_slug if cat_form: if cat_form.is_valid(): cat_form.save() addon.save() else: editable = True if device_type_form: if device_type_form.is_valid(): device_type_form.save(addon) addon.save() else: editable = True else: form = models[section](instance=addon, request=request) else: form = False data = { 'addon': addon, 'webapp': webapp, 'form': form, 'editable': editable, 'tags': tags, 'restricted_tags': restricted_tags, 'cat_form': cat_form, 'preview_form': previews, 'valid_slug': valid_slug, 'device_type_form': device_type_form } return jingo.render(request, 'developers/apps/edit/%s.html' % section, data)
def log_collection(self, num, prefix='foo'): for i in xrange(num): c = Collection.objects.create(name='%s %d' % (prefix, i)) amo.log(amo.LOG.ADD_TO_COLLECTION, self.addon, c)
def publicise(request, addon_id, addon): if addon.status == amo.STATUS_PUBLIC_WAITING: addon.update(status=amo.STATUS_PUBLIC) amo.log(amo.LOG.CHANGE_STATUS, addon.get_status_display(), addon) return redirect(addon.get_dev_url('versions'))
def log_status(self, num): for i in xrange(num): amo.log(amo.LOG.USER_DISABLE, self.addon)
def log_tag(self, num, prefix='foo'): for i in xrange(num): t = Tag.objects.create(tag_text='%s %d' % (prefix, i)) amo.log(amo.LOG.ADD_TAG, self.addon, t)