bgneal@859: """ bgneal@859: ssl_images is a custom manage.py command to convert forum post and comment bgneal@859: images to https. It does this by rewriting the markup: bgneal@859: - Images with src = http://surfguitar101.com/something are rewritten to be bgneal@859: /something. bgneal@859: - Non SG101 images that use http: are downloaded, resized, and uploaded to bgneal@859: an S3 bucket. The src attribute is replaced with the new S3 URL. bgneal@859: """ bgneal@859: import logging bgneal@859: from optparse import make_option bgneal@859: import os.path bgneal@863: import re bgneal@863: import signal bgneal@859: bgneal@859: from django.core.management.base import NoArgsCommand, CommandError bgneal@859: from django.conf import settings bgneal@863: import markdown.inlinepatterns bgneal@859: bgneal@860: from comments.models import Comment bgneal@860: from forums.models import Post bgneal@860: bgneal@860: bgneal@859: LOGFILE = os.path.join(settings.PROJECT_PATH, 'logs', 'ssl_images.log') bgneal@859: logger = logging.getLogger(__name__) bgneal@859: bgneal@863: IMAGE_LINK_RE = re.compile(markdown.inlinepatterns.IMAGE_LINK_RE) bgneal@863: IMAGE_REF_RE = re.compile(markdown.inlinepatterns.IMAGE_REFERENCE_RE) bgneal@863: bgneal@863: quit_flag = False bgneal@863: bgneal@863: bgneal@863: def signal_handler(signum, frame): bgneal@863: """SIGINT signal handler""" bgneal@863: global quit_flag bgneal@863: quit_flag = True bgneal@863: bgneal@859: bgneal@859: def _setup_logging(): bgneal@859: logger.setLevel(logging.DEBUG) bgneal@859: logger.propagate = False bgneal@859: handler = logging.FileHandler(filename=LOGFILE, encoding='utf-8') bgneal@859: formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') bgneal@859: handler.setFormatter(formatter) bgneal@859: logger.addHandler(handler) bgneal@859: bgneal@859: bgneal@860: class CommentFacade(object): bgneal@860: """Wrapper class to provide uniform access to Comments.""" bgneal@860: def __init__(self, comment): bgneal@860: self.comment = comment bgneal@860: bgneal@860: @property bgneal@863: def text(self): bgneal@860: return self.comment.comment bgneal@860: bgneal@863: @text.setter bgneal@863: def text(self, value): bgneal@860: self.comment.comment = value bgneal@860: bgneal@860: bgneal@860: class PostFacade(object): bgneal@860: """Wrapper class to provide uniform access to Forum posts.""" bgneal@860: def __init__(self, post): bgneal@860: self.post = post bgneal@860: bgneal@860: @property bgneal@863: def text(self): bgneal@860: return self.post.body bgneal@860: bgneal@863: @text.setter bgneal@863: def text(self, value): bgneal@860: self.post.body = value bgneal@860: bgneal@860: bgneal@863: def process_post(post): bgneal@863: """Process the post object: bgneal@863: bgneal@863: A regex substitution is run on the post's text field. This fixes up image bgneal@863: links, getting rid of plain old http sources; either converting to https bgneal@863: or relative style links (if the link is to SG101). bgneal@863: bgneal@863: We also do a search for Markdown image reference markup. We aren't expecting bgneal@863: these, but we will log something if we see any. bgneal@863: bgneal@863: """ bgneal@863: bgneal@863: bgneal@859: class Command(NoArgsCommand): bgneal@859: help = "Rewrite forum posts and comments to not use http for images" bgneal@859: option_list = NoArgsCommand.option_list + ( bgneal@859: make_option('--forums', bgneal@859: action='store_true', bgneal@859: default=False, bgneal@860: help="process forum posts"), bgneal@859: make_option('--comments', bgneal@859: action='store_true', bgneal@859: default=False, bgneal@859: help="process comments"), bgneal@860: make_option('-i', '--i', bgneal@859: type='int', bgneal@863: help="optional first slice index; the i in [i:j]"), bgneal@860: make_option('-j', '--j', bgneal@859: type='int', bgneal@863: help="optional second slice index; the j in [i:j]"), bgneal@859: ) bgneal@859: bgneal@859: def handle_noargs(self, **options): bgneal@859: _setup_logging() bgneal@860: logger.info("Starting; arguments received: %s", options) bgneal@859: bgneal@859: do_comments = options['comments'] bgneal@859: do_forums = options['forums'] bgneal@859: if do_comments and do_forums: bgneal@859: raise CommandError("Please specify --forums or --comments, not both") bgneal@859: elif not do_comments and not do_forums: bgneal@859: raise CommandError("Please specify --forums or --comments") bgneal@859: bgneal@860: if do_comments: bgneal@860: qs = Comment.objects.all() bgneal@860: facade = CommentFacade bgneal@860: else: bgneal@860: qs = Post.objects.all() bgneal@860: facade = PostFacade bgneal@860: bgneal@860: i, j = options['i'], options['j'] bgneal@860: bgneal@860: if i is not None and i < 0: bgneal@860: raise CommandError("-i must be >= 0") bgneal@860: if j is not None and j < 0: bgneal@860: raise CommandError("-j must be >= 0") bgneal@860: if j is not None and i is not None and j <= i: bgneal@860: raise CommandError("-j must be > -i") bgneal@860: bgneal@860: if i is not None and j is not None: bgneal@860: qs = qs[i:j] bgneal@860: elif i is not None and j is None: bgneal@860: qs = qs[i:] bgneal@860: elif i is None and j is not None: bgneal@860: qs = qs[:j] bgneal@860: bgneal@863: # Install signal handler for ctrl-c bgneal@863: signal.signal(signal.SIGINT, signal_handler) bgneal@863: bgneal@860: s = [] bgneal@860: for model in qs.iterator(): bgneal@863: if quit_flag: bgneal@863: logger.warning("SIGINT received, exiting") bgneal@860: obj = facade(model) bgneal@863: process_post(obj) bgneal@863: s.append(obj.text) bgneal@860: bgneal@860: import pprint bgneal@860: pprint.pprint(s)