Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Yogendra0Sharma/966a0c3ed01923ba052b288de91d7e9e to your computer and use it in GitHub Desktop.
Save Yogendra0Sharma/966a0c3ed01923ba052b288de91d7e9e to your computer and use it in GitHub Desktop.

Django Weekend Learning

You can override the runserver command (or other management commands and make your own. Django 1.8 doesn't use optparse anymore but I rather like it's parser. This is a quick extension of the runserver command to set an env variable which will disable reCAPTCHA fields.

someapp/profile/management/commands/runserver.py:

from django.contrib.staticfiles.management.commands import runserver
import os

class Command(runserver.Command):
    def add_arguments(self, parser):
        super(Command, self).add_arguments(parser)
        parser.add_argument('--norecaptcha', action='store_false', dest='use_recaptcha', default=True,
                            help='Tells Django to validate all reCAPTCHA fields.')

    def execute(self, *args, **options):
        if options.get('use_recaptcha'):
            os.environ['USE_RECAPTCHA'] = "True"
        super(Command, self).execute(*args, **options)

Integrating reCAPTCHA was very easy. Google sends a g-recaptcha-response GET parameter in the redirect. GET parameters can be grabbed in a widget with value_from_datadict:

class ReCAPTCHAWidget(Widget):
    def render(self, name, value, attrs=None):
        if not settings.USE_RECAPTCHA:
            return 'reCAPTCHA is currently disabled.'
        return """<script src="https://www.google.com/recaptcha/api.js" async defer></script><div class="g-recaptcha" data-sitekey="{}"></div>""".format(settings.RECAPTCHA_SITE_KEY)

    def value_from_datadict(self, data, files, name):
        return data.get('g-recaptcha-response')

Making the validator is then really easy::

class ReCAPTCHAField(Field):
    default_error_messages = {
        'failed': 'Sorry, you did not pass the reCAPTCHA.'
    }

    widget = ReCAPTCHAWidget

    def to_python(self, value):
        return value

    def validate(self, value):
        if not settings.USE_RECAPTCHA:
            return True

        payload = {
            'secret': settings.RECAPTCHA_SECRET_KEY,
            'response': value
        }
        res = requests.post("https://www.google.com/recaptcha/api/siteverify", data=payload)
        res = res.json()
        if not res['success']:
            raise ValidationError(self.error_messages['failed'], code='failed')
        return value

Now I can put a ReCAPTCHAField into any form I want!

I have a Report model that allows users to submit a report which a moderator can then resolve. The user that submitted the report should be able to update the message of the report, but not moderators. Moderators should only be able to resolve the report and see the message, not modify it. h/t to @kennethlove for pointing me in the right direction on this one. Essentially I want to disable a field on a ModelForm given a condition. I can use clean_message to ensure that nobody messes around with the POST data to cheat and I can override __init__ to play around with field widgets. Getting the user instance from the request required overriding get_form_kwargs on the UpdateView child.:

class UpdateReport(UpdateView)
    # ...

    def get_form_kwargs(self):
        kwargs = super(UpdateReport, self).get_form_kwargs()
        kwargs.update({ 'request_user': self.request.user })
        return kwargs

class TextWidget(Widget):
    def render(self, name, value, attrs=None):
        return '<p>{}</p>'.format(value)

class UpdateReportForm(models.ModelForm):
    class Meta:
        model  = Report
        fields = ('resolved', 'message',)

    # The message should be readonly if the user is not the owner. (e.g.
    # moderator is resolving a report)
    # http://stackoverflow.com/questions/324477/in-a-django-form-how-to-make-a-field-readonly-or-disabled-so-that-it-cannot-b
    def __init__(self, *args, **kwargs):
        user = kwargs.pop('request_user')
        super(UpdateReportForm, self).__init__(*args, **kwargs)
        if user.is_moderator:
            self.instance.message_readonly = True
            self.fields['message'].widget = TextWidget()
            self.fields['message'].widget.attrs['readonly'] = True

    def clean_message(self):
        # We don't care what the moderator has to say. Return the original
        # message (it is readonly to moderators).
        if getattr(self.instance, 'message_readonly', False):
            del self.instance.message_readonly
            return self.instance.message
        return self.cleaned_data['message']

My next barrier was custom auth requirements on views. I was tired of overriding dispatch all the time so I went for a mixin. Initially I was hoping to just use django-braces for all my needs, but it wouldn't let me access the request instance where I wanted. So I had to take allauths verified email implementation and mixin (no pun intended, har, it is past midnight) and do the thing. I wanted to utilize messages too:

class VerifiedEmailRequiredMixin(object):
    permission_fail_redirect = '/'
    permission_fail_message = 'You are not authorized to view the requested page.'

    # Taken from verified_email_required
    # https://github.com/pennersr/django-allauth/blob/master/allauth/account/decorators.py
    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_authenticated():
            messages.error(request, 'You must be logged in with a verified email to view the requested page.')
            return redirect(reverse('account_login'))

        if not EmailAddress.objects.filter(user=request.user, verified=True).exists():
            send_email_confirmation(request, request.user)
            messages.error(request, 'You must have a verified email address to view the requested page.')
            return render(request, 'account/verified_email_required.html')

        # The class can define a custom permission test function on top of
        # everything else.
        if getattr(self, 'permission_test', False):
            if not self.permission_test(request, *args, **kwargs):
                messages.error(request, self.permission_fail_message)
                return redirect(self.permission_fail_redirect)

        return super(VerifiedEmailRequiredMixin, self).dispatch(request, *args, **kwargs)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment