diff --git a/meetups/forms.py b/meetups/forms.py index 78653eb..d2ea1f9 100644 --- a/meetups/forms.py +++ b/meetups/forms.py @@ -25,7 +25,8 @@ from collections import namedtuple from wtforms import (Form, validators, TextField, TextAreaField, BooleanField, - IntegerField, HiddenField) + IntegerField, HiddenField, SubmitField) +from .validators import OptionalIf class UserProfileForm(Form): @@ -50,7 +51,8 @@ class VenueEditForm(Form): contact_phone = TextField('Contact Phone', [validators.Required()]) capacity = IntegerField('Maximum Capacity', - [validators.NumberRange(min=0)]) + [OptionalIf(field='delete'), validators.Required(), + validators.NumberRange(min=0)]) # Optional questionnaire fields need_names = BooleanField('A list of names is required ahead of time.') @@ -60,6 +62,9 @@ class VenueEditForm(Form): instructions = TextAreaField('Special instructions ' '(e.g., take a particular evelvator, use a specific door)') + delete = SubmitField('Delete') + undelete = SubmitField('Restore') + class VenueClaimForm(VenueEditForm): """Extends the :class:`~meetup.forms.VenueEditForm` to add a confirmation diff --git a/meetups/templates/account/venues.html b/meetups/templates/account/venues.html index fd2fa90..787207d 100644 --- a/meetups/templates/account/venues.html +++ b/meetups/templates/account/venues.html @@ -15,6 +15,9 @@

My Spaces

{% for venue in venues %}
  • {{ venue }}

    + {% if venue.deleted %} + deleted + {% endif %} {% for tag in venue.taglist %} {{ tag }} {% endfor %} diff --git a/meetups/templates/venue/claim.html b/meetups/templates/venue/claim.html index b1f7ada..4b5e4c0 100644 --- a/meetups/templates/venue/claim.html +++ b/meetups/templates/venue/claim.html @@ -43,13 +43,19 @@

    {{ venue }}

    {%- endif -%} - + + {% if venue.deleted -%} + + {%- else -%} + + {%- endif -%} {%- endblock %} diff --git a/meetups/validators.py b/meetups/validators.py new file mode 100644 index 0000000..245e477 --- /dev/null +++ b/meetups/validators.py @@ -0,0 +1,41 @@ +# Copyright (c) 2012, The NYC Python Meetup +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from wtforms import validators + + +class OptionalIf(object): + """Make a form field optional if a specific field has a value. + + This validator can be chained together with other validators such as + :class:`~wtforms.validators.Required` and override them. + """ + def __init__(self, field): + self.field = field + + def __call__(self, form, field): + if self.field in form and form[self.field].data: + field.errors[:] = [] + raise validators.StopValidation() diff --git a/meetups/views.py b/meetups/views.py index 067f49f..4f75862 100644 --- a/meetups/views.py +++ b/meetups/views.py @@ -110,7 +110,7 @@ def logout(): @login_required def need(): user = User(_id=int(session['member_id'])).load() - groups = get_groups({'_id': {'$in': user.organizer_of}}) + groups = get_groups({'_id': {'$in': user.organizer_of, 'deleted': False}}) return render_template('need.html', user=user, groups=groups, @@ -283,12 +283,24 @@ def get_contact_field(attr): form = form_class(request.form, obj=venue) if request.method == 'POST' and form.validate(): - venue.claim(contact_name=form.contact_name.data, - contact_email=form.contact_email.data, - contact_phone=form.contact_phone.data, user_id=user._id, - capacity=form.capacity.data, need_names=form.need_names.data, - food=form.food.data, av=form.av.data, chairs=form.chairs.data, - instructions=form.instructions.data) + claim = { + 'contact_name': form.contact_name.data, + 'contact_email': form.contact_email.data, + 'contact_phone': form.contact_phone.data, + 'user_id': user._id, + 'capacity': form.capacity.data, + 'need_names': form.need_names.data, + 'food': form.food.data, + 'av': form.av.data, + 'chairs': form.chairs.data, + 'instructions': form.instructions.data + } + if 'delete' in request.form: + claim['deleted'] = True + elif 'undelete' in request.form: + claim['deleted'] = False + + venue.claim(**claim) flash('Thank you for %s %s' % ( 'updating' if venue.claimed else 'claiming', venue.name), 'success') diff --git a/tests/test_validators.py b/tests/test_validators.py new file mode 100644 index 0000000..b8c25b0 --- /dev/null +++ b/tests/test_validators.py @@ -0,0 +1,64 @@ +from unittest import TestCase + +from meetups.validators import OptionalIf +from wtforms.validators import StopValidation + + +# The Dummy* classes are borrowed from tests.validators in wtforms +class DummyTranslations(object): + def gettext(self, string): + return string + + def ngettext(self, singular, plural, n): + if n == 1: + return singular + + return plural + + +class DummyForm(dict): + pass + + +class DummyField(object): + _translations = DummyTranslations() + + def __init__(self, data, errors=(), raw_data=None): + self.data = data + self.errors = list(errors) + self.raw_data = raw_data + + def gettext(self, string): + return self._translations.gettext(string) + + def ngettext(self, singular, plural, n): + return self._translations.ngettext(singular, plural, n) + + +class ValidatorsTest(TestCase): + """Test custom validators""" + def setUp(self): + self.form = DummyForm() + + def test_optional_if_field_does_not_exist(self): + (u"Test that the field doesn't pass the optional validation when " + "the other field doesn't exist.") + self.assertEqual( + OptionalIf('spam')(self.form, DummyField('eggs')), + None + ) + + def test_optional_if_field_exists(self): + (u'Test that the field is optional when the other field exists.') + self.form['spam'] = DummyField(True) + self.form['eggs'] = DummyField(None, ['Error message']) + + # When the field exists, OptionalIf should raise OptionalIf and the + # `len()` of its `errors` attribute should be 0. + self.assertRaises( + StopValidation, + OptionalIf('spam'), + self.form, + self.form['eggs'] + ) + self.assertEqual(len(self.form['eggs'].errors), 0)