def crop_and_upload_image_to_popit(self, image_filename, crop_bounds, moderator_why_allowed, make_primary): original = Image.open(image_filename) # Some uploaded images are CYMK, which gives you an error when # you try to write them as PNG, so convert to RGB: original = original.convert('RGB') cropped = original.crop(crop_bounds) ntf = NamedTemporaryFile(delete=False) cropped.save(ntf.name, 'PNG') # Upload the image to PopIt... person_id = self.queued_image.popit_person_id person = PopItPerson.create_from_popit(self.api, person_id) image_upload_url = '{base}persons/{person_id}/image'.format( base=get_base_url(), person_id=person_id) data = { 'md5sum': get_file_md5sum(ntf.name), 'user_why_allowed': self.queued_image.why_allowed, 'user_justification_for_use': self.queued_image.justification_for_use, 'moderator_why_allowed': moderator_why_allowed, 'mime_type': 'image/png', 'notes': _('Approved from photo moderation queue'), 'uploaded_by_user': self.queued_image.user.username, 'created': None, } if make_primary: data['index'] = 'first' with open(ntf.name) as f: requests.post(image_upload_url, data=data, files={'image': f.read()}, headers={'APIKey': self.api.api_key}) person.invalidate_cache_entries() # Remove the cropped temporary image file: os.remove(ntf.name)
def handle(self, *args, **options): self.verbosity = int(options.get('verbosity', 1)) api = create_popit_api_object() if len(args) != 2: raise CommandError("You must provide all two arguments") person_id, other_name = args person = PopItPerson.create_from_popit(api, person_id) person.other_names.append( { 'name': other_name, 'note': options['note'], 'start_date': options['start_date'], 'end_date': options['end_date'] } ) person.save_to_popit(api) person.invalidate_cache_entries() # FIXME: this should create a new version in the versions # array too, otherwise you manually have to edit on YourNextMP # too to create a new version with a change message. print "Successfully updated {0}".format(person_id)
def upload_photo(request, popit_person_id): if request.method == 'POST': form = UploadPersonPhotoForm(request.POST, request.FILES) if form.is_valid(): # Make sure that we save the user that made the upload queued_image = form.save(commit=False) queued_image.user = request.user queued_image.save() # Record that action: LoggedAction.objects.create( user=request.user, action_type='photo-upload', ip_address=get_client_ip(request), popit_person_new_version='', popit_person_id=popit_person_id, source=form.cleaned_data['justification_for_use'], ) return HttpResponseRedirect( reverse('photo-upload-success', kwargs={ 'popit_person_id': form.cleaned_data['popit_person_id'] })) else: form = UploadPersonPhotoForm( initial={'popit_person_id': popit_person_id}) api = create_popit_api_object() return render( request, 'moderation_queue/photo-upload-new.html', { 'form': form, 'person': PopItPerson.create_from_popit(api, popit_person_id) })
def handle(self, *args, **options): self.verbosity = int(options.get('verbosity', 1)) api = create_popit_api_object() if len(args) != 2: raise CommandError("You must provide all two arguments") person_id, other_name = args person = PopItPerson.create_from_popit(api, person_id) person.other_names.append( { 'name': other_name, 'note': options['note'], 'start_date': options['start_date'], 'end_date': options['end_date'] } ) person.save_to_popit(api) person.invalidate_cache_entries() # FIXME: this should create a new version in the versions # array too, otherwise you manually have to edit on the # YourNextRepresentative site too to create a new version with # a change message. print "Successfully updated {0}".format(person_id)
def get_context_data(self, **kwargs): context = super(PhotoUploadSuccess, self).get_context_data(**kwargs) context['person'] = PopItPerson.create_from_popit( self.api, kwargs['popit_person_id'] ) return context
def handle(self, *args, **options): self.verbosity = int(options.get('verbosity', 1)) api = create_popit_api_object() if len(args) != 3: raise CommandError("You must provide all three arguments") person_id, scheme, identifier = args person = PopItPerson.create_from_popit(api, person_id) person.identifiers.append( { 'scheme': scheme, 'identifier': identifier, } ) person.save_to_popit(api) person.invalidate_cache_entries() # FIXME: this should create a new version in the versions # array too, otherwise you manually have to edit on YourNextMP # too to create a new version with a change message. print "Successfully updated {0}".format(person_id)
def get_context_data(self, **kwargs): context = super(PhotoReview, self).get_context_data(**kwargs) self.queued_image = get_object_or_404( QueuedImage, pk=kwargs['queued_image_id'] ) context['queued_image'] = self.queued_image person = PopItPerson.create_from_popit( self.api, self.queued_image.popit_person_id, ) context['has_crop_bounds'] = int(self.queued_image.has_crop_bounds) max_x = self.queued_image.image.width - 1 max_y = self.queued_image.image.height - 1 guessed_crop_bounds = [ value_if_none(self.queued_image.crop_min_x, 0), value_if_none(self.queued_image.crop_min_y, 0), value_if_none(self.queued_image.crop_max_x, max_x), value_if_none(self.queued_image.crop_max_y, max_y), ] context['form'] = PhotoReviewForm( initial = { 'queued_image_id': self.queued_image.id, 'decision': self.queued_image.decision, 'x_min': guessed_crop_bounds[0], 'y_min': guessed_crop_bounds[1], 'x_max': guessed_crop_bounds[2], 'y_max': guessed_crop_bounds[3], 'moderator_why_allowed': self.queued_image.why_allowed, 'make_primary': True, } ) context['guessed_crop_bounds'] = guessed_crop_bounds context['why_allowed'] = self.queued_image.why_allowed context['moderator_why_allowed'] = self.queued_image.why_allowed # There are often source links supplied in the justification, # and it's convenient to be able to follow them. However, make # sure that any maliciously added HTML tags have been stripped # before linkifying any URLs: context['justification_for_use'] = \ bleach.linkify( bleach.clean( self.queued_image.justification_for_use, tags=[], strip=True ) ) context['google_image_search_url'] = self.get_google_image_search_url( person ) context['google_reverse_image_search_url'] = \ self.get_google_reverse_image_search_url( self.queued_image.image.url ) context['person'] = person return context
def handle(self, *args, **kwargs): if len(args) < 1: raise CommandError("You must provide one or more PopIt person ID") for person_id in args: person = PopItPerson.create_from_popit( create_popit_api_object(), person_id ) person.delete_memberships() self.create_party_memberships(person_id, person.popit_data) self.create_candidate_list_memberships(person_id, person.popit_data)
def get_context_data(self, **kwargs): context = super(PhotoReview, self).get_context_data(**kwargs) self.queued_image = get_object_or_404(QueuedImage, pk=kwargs['queued_image_id']) context['queued_image'] = self.queued_image person = PopItPerson.create_from_popit( self.api, self.queued_image.popit_person_id, ) context['has_crop_bounds'] = int(self.queued_image.has_crop_bounds) max_x = self.queued_image.image.width - 1 max_y = self.queued_image.image.height - 1 guessed_crop_bounds = [ value_if_none(self.queued_image.crop_min_x, 0), value_if_none(self.queued_image.crop_min_y, 0), value_if_none(self.queued_image.crop_max_x, max_x), value_if_none(self.queued_image.crop_max_y, max_y), ] context['form'] = PhotoReviewForm( initial={ 'queued_image_id': self.queued_image.id, 'decision': self.queued_image.decision, 'x_min': guessed_crop_bounds[0], 'y_min': guessed_crop_bounds[1], 'x_max': guessed_crop_bounds[2], 'y_max': guessed_crop_bounds[3], 'moderator_why_allowed': self.queued_image.why_allowed, 'make_primary': True, }) context['guessed_crop_bounds'] = guessed_crop_bounds context['why_allowed'] = self.queued_image.why_allowed context['moderator_why_allowed'] = self.queued_image.why_allowed # There are often source links supplied in the justification, # and it's convenient to be able to follow them. However, make # sure that any maliciously added HTML tags have been stripped # before linkifying any URLs: context['justification_for_use'] = \ bleach.linkify( bleach.clean( self.queued_image.justification_for_use, tags=[], strip=True ) ) context['google_image_search_url'] = self.get_google_image_search_url( person) context['google_reverse_image_search_url'] = \ self.get_google_reverse_image_search_url( self.queued_image.image.url ) context['person'] = person return context
def handle(self, *args, **options): from candidates.models import PopItPerson from candidates.popit import create_popit_api_object self.verbosity = int(options.get('verbosity', 1)) api = create_popit_api_object() if len(args) != 1: raise CommandError("You must provide a person.js URL") person_js_url = args[0] people_data = requests.get(person_js_url).json() for person_data in people_data['persons']: twfy_person = PopItPerson.create_from_dict(person_data) ynmp_id = twfy_person.get_identifier('yournextmp') if not ynmp_id: continue parlparse_id = twfy_person.id ynmp_person = PopItPerson.create_from_popit(api, ynmp_id) existing_parlparse_id = ynmp_person.get_identifier('uk.org.publicwhip') if existing_parlparse_id: if existing_parlparse_id == parlparse_id: # That's fine, there's already the right parlparse ID pass else: # Otherwise there's a mismatch, which needs investigation msg = "Warning: parlparse ID mismatch between YNMP {0} " msg += "and TWFY {1} for YNMP person {2}\n" self.stderr.write( msg.format( existing_parlparse_id, parlparse_id, ynmp_id, ) ) continue msg = "Updating the YourNextMP person {0} with parlparse_id {1}\n" self.stdout.write(msg.format(ynmp_id, parlparse_id)) ynmp_person.set_identifier( 'uk.org.publicwhip', parlparse_id, ) change_metadata = get_change_metadata( None, "Fetched a new parlparse ID" ) ynmp_person.record_version(change_metadata) ynmp_person.save_to_popit(api) ynmp_person.invalidate_cache_entries()
def handle(self, *args, **options): from candidates.models import PopItPerson from candidates.popit import create_popit_api_object self.verbosity = int(options.get('verbosity', 1)) api = create_popit_api_object() if len(args) != 1: raise CommandError("You must provide a person.js URL") person_js_url = args[0] people_data = requests.get(person_js_url).json() for person_data in people_data['persons']: twfy_person = PopItPerson.create_from_dict(person_data) ynmp_id = twfy_person.get_identifier('yournextmp') if not ynmp_id: continue parlparse_id = twfy_person.id ynmp_person = PopItPerson.create_from_popit(api, ynmp_id) existing_parlparse_id = ynmp_person.get_identifier( 'uk.org.publicwhip') if existing_parlparse_id: if existing_parlparse_id == parlparse_id: # That's fine, there's already the right parlparse ID pass else: # Otherwise there's a mismatch, which needs investigation msg = "Warning: parlparse ID mismatch between YNMP {0} " msg += "and TWFY {1} for YNMP person {2}\n" self.stderr.write( msg.format( existing_parlparse_id, parlparse_id, ynmp_id, )) continue msg = "Updating the YourNextMP person {0} with parlparse_id {1}\n" self.stdout.write(msg.format(ynmp_id, parlparse_id)) ynmp_person.set_identifier( 'uk.org.publicwhip', parlparse_id, ) change_metadata = get_change_metadata( None, "Fetched a new parlparse ID") ynmp_person.record_version(change_metadata) ynmp_person.save_to_popit(api) ynmp_person.invalidate_cache_entries()
def handle(self, *args, **kwargs): api = create_popit_api_object() if len(args) < 1: raise CommandError("You must provide one or more PopIt person ID") for person_id in args: invalidate_person(person_id) person = PopItPerson.create_from_popit(api, person_id) posts_to_invalidate = person.get_associated_posts() person.delete_memberships(api) # The memberships are recreated when you assign to # standing_in and party_memberships; this script assumes # these are correct and so re-setting these should # recreate the memberships correctly. person.standing_in = person.standing_in person.party_memberships = person.party_memberships person.save_to_popit(api) invalidate_posts(posts_to_invalidate) invalidate_person(person_id)
def handle(self, *args, **kwargs): api = create_popit_api_object() if len(args) < 1: raise CommandError("You must provide one or more PopIt person ID") for person_id in args: invalidate_person(person_id) person = PopItPerson.create_from_popit(api, person_id) posts_to_invalidate = person.get_associated_posts() person.delete_memberships(api) # The memberships are recreated when you assign to # standing_in and party_memberships; this script assumes # these are correct and so re-setting these should # recreate the memberships correctly. person.standing_in = person.standing_in person.party_memberships = person.party_memberships person.save_to_popit(api) invalidate_posts(posts_to_invalidate) invalidate_person(person_id)
def upload_photo(request, popit_person_id): if request.method == 'POST': form = UploadPersonPhotoForm(request.POST, request.FILES) if form.is_valid(): # Make sure that we save the user that made the upload queued_image = form.save(commit=False) queued_image.user = request.user queued_image.save() # Record that action: LoggedAction.objects.create( user=request.user, action_type='photo-upload', ip_address=get_client_ip(request), popit_person_new_version='', popit_person_id=popit_person_id, source=form.cleaned_data['justification_for_use'], ) return HttpResponseRedirect(reverse( 'photo-upload-success', kwargs={ 'popit_person_id': form.cleaned_data['popit_person_id'] } )) else: form = UploadPersonPhotoForm( initial={ 'popit_person_id': popit_person_id } ) api = create_popit_api_object() return render( request, 'moderation_queue/photo-upload-new.html', {'form': form, 'queued_images': QueuedImage.objects.filter( popit_person_id=popit_person_id, decision='undecided', ).order_by('created'), 'person': PopItPerson.create_from_popit(api, popit_person_id)} )
def crop_and_upload_image_to_popit(self, image_filename, crop_bounds, moderator_why_allowed, make_primary): original = Image.open(image_filename) # Some uploaded images are CYMK, which gives you an error when # you try to write them as PNG, so convert to RGBA (this is # RGBA rather than RGB so that any alpha channel (transparency) # is preserved). original = original.convert('RGBA') cropped = original.crop(crop_bounds) ntf = NamedTemporaryFile(delete=False) cropped.save(ntf.name, 'PNG') # Upload the image to PopIt... person_id = self.queued_image.popit_person_id person = PopItPerson.create_from_popit(self.api, person_id) image_upload_url = '{base}persons/{person_id}/image'.format( base=get_base_url(), person_id=person_id ) data = { 'md5sum': get_file_md5sum(ntf.name), 'user_why_allowed': self.queued_image.why_allowed, 'user_justification_for_use': self.queued_image.justification_for_use, 'moderator_why_allowed': moderator_why_allowed, 'mime_type': 'image/png', 'notes': _('Approved from photo moderation queue'), 'uploaded_by_user': self.queued_image.user.username, 'created': None, } if make_primary: data['index'] = 'first' with open(ntf.name) as f: requests.post( image_upload_url, data=data, files={'image': f.read()}, headers={'APIKey': self.api.api_key} ) person.invalidate_cache_entries() # Remove the cropped temporary image file: os.remove(ntf.name)
def form_valid(self, form): decision = form.cleaned_data['decision'] person = PopItPerson.create_from_popit( self.api, self.queued_image.popit_person_id ) candidate_path = person.get_absolute_url() candidate_name = person.name candidate_link = u'<a href="{url}">{name}</a>'.format( url=candidate_path, name=candidate_name, ) photo_review_url = self.request.build_absolute_uri( self.queued_image.get_absolute_url() ) def flash(level, message): messages.add_message( self.request, level, message, extra_tags='safe photo-review' ) if decision == 'approved': # Crop the image... crop_fields = ('x_min', 'y_min', 'x_max', 'y_max') self.crop_and_upload_image_to_popit( self.queued_image.image.path, [form.cleaned_data[e] for e in crop_fields], form.cleaned_data['moderator_why_allowed'], form.cleaned_data['make_primary'], ) self.queued_image.decision = 'approved' for i, field in enumerate(crop_fields): setattr( self.queued_image, 'crop_' + field, form.cleaned_data[field] ) self.queued_image.save() update_message = _(u'Approved a photo upload from ' u'{uploading_user} who provided the message: ' u'"{message}"').format( uploading_user=self.queued_image.user.username, message=self.queued_image.justification_for_use, ) change_metadata = get_change_metadata( self.request, update_message ) # We have to refetch the person from PopIt, otherwise # saving the new version will write back the images array # from before we uploaded the image: person = PopItPerson.create_from_popit(self.api, person.id) person.record_version(change_metadata) person.save_to_popit(self.api, self.request.user) LoggedAction.objects.create( user=self.request.user, action_type='photo-approve', ip_address=get_client_ip(self.request), popit_person_new_version=change_metadata['version_id'], popit_person_id=self.queued_image.popit_person_id, source=update_message, ) self.send_mail( _('YourNextMP image upload approved'), render_to_string( 'moderation_queue/photo_approved_email.txt', {'candidate_page_url': person.get_absolute_url(self.request)} ), ) flash( messages.SUCCESS, _(u'You approved a photo upload for %s') % candidate_link ) elif decision == 'rejected': self.queued_image.decision = 'rejected' self.queued_image.save() update_message = _(u'Rejected a photo upload from ' u'{uploading_user}').format( uploading_user=self.queued_image.user.username, ) LoggedAction.objects.create( user=self.request.user, action_type='photo-reject', ip_address=get_client_ip(self.request), popit_person_new_version='', popit_person_id=self.queued_image.popit_person_id, source=update_message, ) retry_upload_link = self.request.build_absolute_uri( reverse( 'photo-upload', kwargs={'popit_person_id': self.queued_image.popit_person_id} ) ) self.send_mail( _('YourNextMP image moderation results'), render_to_string( 'moderation_queue/photo_rejected_email.txt', {'reason': form.cleaned_data['rejection_reason'], 'candidate_name': candidate_name, 'retry_upload_link': retry_upload_link, 'photo_review_url': photo_review_url}, ), email_support_too=True, ) flash( messages.INFO, _(u'You rejected a photo upload for %s') % candidate_link ) elif decision == 'undecided': # If it's left as undecided, just redirect back to the # photo review queue... flash( messages.INFO, _(u'You left a photo upload for {0} in the queue').format( candidate_link ) ) elif decision == 'ignore': self.queued_image.decision = 'ignore' self.queued_image.save() update_message = _(u'Ignored a photo upload from ' u'{uploading_user} (This usually means it was a duplicate)').format( uploading_user=self.queued_image.user.username) LoggedAction.objects.create( user=self.request.user, action_type='photo-ignore', ip_address=get_client_ip(self.request), popit_person_new_version='', popit_person_id=self.queued_image.popit_person_id, source=update_message, ) flash( messages.INFO, _(u'You indicated a photo upload for {0} should be ignored').format( candidate_link ) ) else: raise Exception("BUG: unexpected decision {0}".format(decision)) return HttpResponseRedirect(reverse('photo-review-list'))
def get_context_data(self, **kwargs): context = super(PhotoUploadSuccess, self).get_context_data(**kwargs) context['person'] = PopItPerson.create_from_popit( self.api, kwargs['popit_person_id']) return context
def form_valid(self, form): decision = form.cleaned_data['decision'] person = PopItPerson.create_from_popit( self.api, self.queued_image.popit_person_id) candidate_path = person.get_absolute_url() candidate_name = person.name candidate_link = u'<a href="{url}">{name}</a>'.format( url=candidate_path, name=candidate_name, ) photo_review_url = self.request.build_absolute_uri( self.queued_image.get_absolute_url()) def flash(level, message): messages.add_message(self.request, level, message, extra_tags='safe photo-review') if decision == 'approved': # Crop the image... crop_fields = ('x_min', 'y_min', 'x_max', 'y_max') self.crop_and_upload_image_to_popit( self.queued_image.image.path, [form.cleaned_data[e] for e in crop_fields], form.cleaned_data['moderator_why_allowed'], form.cleaned_data['make_primary'], ) self.queued_image.decision = 'approved' for i, field in enumerate(crop_fields): setattr(self.queued_image, 'crop_' + field, form.cleaned_data[field]) self.queued_image.save() update_message = _( u'Approved a photo upload from ' u'{uploading_user} who provided the message: ' u'"{message}"').format( uploading_user=self.queued_image.user.username, message=self.queued_image.justification_for_use, ) change_metadata = get_change_metadata(self.request, update_message) # We have to refetch the person from PopIt, otherwise # saving the new version will write back the images array # from before we uploaded the image: person = PopItPerson.create_from_popit(self.api, person.id) person.record_version(change_metadata) person.save_to_popit(self.api, self.request.user) LoggedAction.objects.create( user=self.request.user, action_type='photo-approve', ip_address=get_client_ip(self.request), popit_person_new_version=change_metadata['version_id'], popit_person_id=self.queued_image.popit_person_id, source=update_message, ) self.send_mail( _('YourNextMP image upload approved'), render_to_string('moderation_queue/photo_approved_email.txt', { 'candidate_page_url': person.get_absolute_url(self.request) }), ) flash(messages.SUCCESS, _(u'You approved a photo upload for %s') % candidate_link) elif decision == 'rejected': self.queued_image.decision = 'rejected' self.queued_image.save() update_message = _( u'Rejected a photo upload from ' u'{uploading_user}').format( uploading_user=self.queued_image.user.username, ) LoggedAction.objects.create( user=self.request.user, action_type='photo-reject', ip_address=get_client_ip(self.request), popit_person_new_version='', popit_person_id=self.queued_image.popit_person_id, source=update_message, ) retry_upload_link = self.request.build_absolute_uri( reverse('photo-upload', kwargs={ 'popit_person_id': self.queued_image.popit_person_id })) self.send_mail( _('YourNextMP image moderation results'), render_to_string( 'moderation_queue/photo_rejected_email.txt', { 'reason': form.cleaned_data['rejection_reason'], 'candidate_name': candidate_name, 'retry_upload_link': retry_upload_link, 'photo_review_url': photo_review_url }, ), email_support_too=True, ) flash(messages.INFO, _(u'You rejected a photo upload for %s') % candidate_link) elif decision == 'undecided': # If it's left as undecided, just redirect back to the # photo review queue... flash( messages.INFO, _(u'You left a photo upload for {0} in the queue').format( candidate_link)) elif decision == 'ignore': self.queued_image.decision = 'ignore' self.queued_image.save() update_message = _( u'Ignored a photo upload from ' u'{uploading_user} (This usually means it was a duplicate)' ).format(uploading_user=self.queued_image.user.username) LoggedAction.objects.create( user=self.request.user, action_type='photo-ignore', ip_address=get_client_ip(self.request), popit_person_new_version='', popit_person_id=self.queued_image.popit_person_id, source=update_message, ) flash( messages.INFO, _(u'You indicated a photo upload for {0} should be ignored'). format(candidate_link)) else: raise Exception("BUG: unexpected decision {0}".format(decision)) return HttpResponseRedirect(reverse('photo-review-list'))