Commit e702264f authored by Olga Brani's avatar Olga Brani
Browse files

Back up file

parent 0dd894ec
......@@ -41,7 +41,10 @@ from astakos.im.util import get_invitation
from astakos.im.functions import (send_verification, send_activation,
send_account_creation_notification,
send_group_creation_notification, activate)
from astakos.im.settings import INVITATIONS_ENABLED, MODERATION_ENABLED, SITENAME, RE_USER_EMAIL_PATTERNS
from astakos.im.settings import (INVITATIONS_ENABLED, MODERATION_ENABLED,
SITENAME, RE_USER_EMAIL_PATTERNS
)
from astakos.im.messages import as astakos_messages
import logging
import re
......@@ -226,28 +229,23 @@ class ActivationResult(object):
class VerificationSent(ActivationResult):
def __init__(self):
message = _('Verification sent.')
message = _(astakos_messages.VERIFICATION_SENT)
super(VerificationSent, self).__init__(message)
class SwitchAccountsVerificationSent(ActivationResult):
def __init__(self, email):
message = _('This email is already associated with another \
local account. To change this account to a shibboleth \
one follow the link in the verification email sent \
to %s. Otherwise just ignore it.' % email)
message = _(astakos_messages.SWITCH_ACCOUNT_LINK_SENT)
super(SwitchAccountsVerificationSent, self).__init__(message)
class NotificationSent(ActivationResult):
def __init__(self):
message = _('Your request for an account was successfully received and is now pending \
approval. You will be notified by email in the next few days. Thanks for \
your interest in ~okeanos! The GRNET team.')
message = _(astakos_messages.NOTIFACATION_SENT)
super(NotificationSent, self).__init__(message)
class RegistationCompleted(ActivationResult):
def __init__(self):
message = _('Registration completed. You can now login.')
message = _(astakos_messages.REGISTRATION_COMPLETED)
super(RegistationCompleted, self).__init__(message)
......@@ -47,8 +47,11 @@ if QUOTA_HOLDER_URL:
ENTITY_KEY = '1'
inf = float('inf')
logger = logging.getLogger(__name__)
inf = float('inf')
def call(func_name):
"""Decorator function for QuotaholderHTTP client calls."""
......@@ -88,7 +91,7 @@ def send_quota(users, client=None):
for resource, uplimit in user.quota.iteritems():
key = ENTITY_KEY
quantity = None
capacity = uplimit
capacity = uplimit if uplimit != inf else None
import_limit = None
export_limit = None
flags = 0
......
......@@ -60,6 +60,8 @@ from astakos.im.functions import send_change_email
from astakos.im.util import reserved_email, get_query
import astakos.im.messages as astakos_messages
import logging
import hashlib
import recaptcha.client.captcha as captcha
......@@ -116,15 +118,15 @@ class LocalUserCreationForm(UserCreationForm):
def clean_email(self):
email = self.cleaned_data['email']
if not email:
raise forms.ValidationError(_("This field is required"))
raise forms.ValidationError(_(astakos_messages.REQUIRED_FIELD))
if reserved_email(email):
raise forms.ValidationError(_("This email is already used"))
raise forms.ValidationError(_(astakos_messages.EMAIL_USED))
return email
def clean_has_signed_terms(self):
has_signed_terms = self.cleaned_data['has_signed_terms']
if not has_signed_terms:
raise forms.ValidationError(_('You have to agree with the terms'))
raise forms.ValidationError(_(astakos_messages.SIGN_TERMS))
return has_signed_terms
def clean_recaptcha_response_field(self):
......@@ -142,8 +144,7 @@ class LocalUserCreationForm(UserCreationForm):
rrf = self.cleaned_data['recaptcha_response_field']
check = captcha.submit(rcf, rrf, RECAPTCHA_PRIVATE_KEY, self.ip)
if not check.is_valid:
raise forms.ValidationError(
_('You have not entered the correct words'))
raise forms.ValidationError(_(astakos_messages.CAPTCHA_VALIDATION_ERR))
def save(self, commit=True):
"""
......@@ -222,13 +223,13 @@ class ThirdPartyUserCreationForm(forms.ModelForm):
def clean_email(self):
email = self.cleaned_data['email']
if not email:
raise forms.ValidationError(_("This field is required"))
raise forms.ValidationError(_(astakos_messages.REQUIRED_FIELD))
return email
def clean_has_signed_terms(self):
has_signed_terms = self.cleaned_data['has_signed_terms']
if not has_signed_terms:
raise forms.ValidationError(_('You have to agree with the terms'))
raise forms.ValidationError(_(astakos_messages.SIGN_TERMS))
return has_signed_terms
def save(self, commit=True):
......@@ -287,10 +288,9 @@ class ShibbolethUserCreationForm(ThirdPartyUserCreationForm):
email = self.cleaned_data['email']
for user in AstakosUser.objects.filter(email=email):
if user.provider == 'shibboleth':
raise forms.ValidationError(_("This email is already associated with another shibboleth account."))
raise forms.ValidationError(_(astakos_messages.SHIBBOLETH_EMAIL_USED))
elif not user.is_active:
raise forms.ValidationError(_("This email is already associated with an inactive account. \
You need to wait to be activated before being able to switch to a shibboleth account."))
raise forms.ValidationError(_(astakos_messages.SHIBBOLETH_INACTIVE_ACC))
super(ShibbolethUserCreationForm, self).clean_email()
return email
......@@ -343,13 +343,12 @@ class LoginForm(AuthenticationForm):
rrf = self.cleaned_data['recaptcha_response_field']
check = captcha.submit(rcf, rrf, RECAPTCHA_PRIVATE_KEY, self.ip)
if not check.is_valid:
raise forms.ValidationError(
_('You have not entered the correct words'))
raise forms.ValidationError(_(astakos_messages.CAPTCHA_VALIDATION_ERR))
def clean(self):
super(LoginForm, self).clean()
if self.user_cache and self.user_cache.provider not in ('local', ''):
raise forms.ValidationError(_('Local login is not the current authentication method for this account.'))
raise forms.ValidationError(_(astakos_messages.SUSPENDED_LOCAL_ACC))
return self.cleaned_data
......@@ -419,10 +418,9 @@ class ExtendedPasswordResetForm(PasswordResetForm):
try:
user = AstakosUser.objects.get(email=email, is_active=True)
if not user.has_usable_password():
raise forms.ValidationError(
_("This account has not a usable password."))
raise forms.ValidationError(_(astakos_messages.UNUSABLE_PASSWORD))
except AstakosUser.DoesNotExist:
raise forms.ValidationError(_('That e-mail address doesn\'t have an associated user account. Are you sure you\'ve registered?'))
raise forms.ValidationError(_(astakos_messages.EMAIL_UNKNOWN))
return email
def save(
......@@ -460,7 +458,7 @@ class EmailChangeForm(forms.ModelForm):
def clean_new_email_address(self):
addr = self.cleaned_data['new_email_address']
if AstakosUser.objects.filter(email__iexact=addr):
raise forms.ValidationError(_(u'This email address is already in use. Please supply a different email address.'))
raise forms.ValidationError(_(astakos_messages.EMAIL_USED))
return addr
def save(self, email_template_name, request, commit=True):
......@@ -485,7 +483,7 @@ class SignApprovalTermsForm(forms.ModelForm):
def clean_has_signed_terms(self):
has_signed_terms = self.cleaned_data['has_signed_terms']
if not has_signed_terms:
raise forms.ValidationError(_('You have to agree with the terms'))
raise forms.ValidationError(_(astakos_messages.SIGN_TERMS))
return has_signed_terms
......@@ -503,8 +501,7 @@ class InvitationForm(forms.ModelForm):
username = self.cleaned_data['username']
try:
Invitation.objects.get(username=username)
raise forms.ValidationError(
_('There is already invitation for this email.'))
raise forms.ValidationError(_(astakos_messages.INVITATION_EMAIL_EXISTS))
except Invitation.DoesNotExist:
pass
return username
......@@ -690,7 +687,7 @@ class AstakosGroupUpdateForm(forms.ModelForm):
class AddGroupMembersForm(forms.Form):
q = forms.CharField(
max_length=800, widget=forms.Textarea, label=_('Add users'),
help_text=_('Add comma separated user emails, eg. user1@user.com, user2@user.com'),
help_text=_(astakos_messages.ADD_GROUP_MEMBERS_Q_HELP),
required=True)
def clean(self):
......@@ -700,8 +697,7 @@ class AddGroupMembersForm(forms.Form):
db_entries = AstakosUser.objects.filter(email__in=users)
unknown = list(set(users) - set(u.email for u in db_entries))
if unknown:
raise forms.ValidationError(
_('Unknown users: %s' % ','.join(unknown)))
raise forms.ValidationError(_(astakos_messages.UNKNOWN_USERS) % ','.join(unknown))
self.valid_users = db_entries
return self.cleaned_data
......
......@@ -61,6 +61,7 @@ from astakos.im.settings import (DEFAULT_CONTACT_EMAIL, SITENAME, BASEURL,
FEEDBACK_EMAIL_SUBJECT,
EMAIL_CHANGE_EMAIL_SUBJECT)
import astakos.im.models
import astakos.im.messages as astakos_messages
logger = logging.getLogger(__name__)
......@@ -303,41 +304,41 @@ class SendMailError(Exception):
class SendAdminNotificationError(SendMailError):
def __init__(self):
self.message = _('Failed to send notification')
self.message = _(astakos_messages.ADMIN_NOTIFICATION_SEND_ERR)
super(SendAdminNotificationError, self).__init__()
class SendVerificationError(SendMailError):
def __init__(self):
self.message = _('Failed to send verification')
self.message = _(astakos_messages.VERIFICATION_SEND_ERR)
super(SendVerificationError, self).__init__()
class SendInvitationError(SendMailError):
def __init__(self):
self.message = _('Failed to send invitation')
self.message = _(astakos_messages.INVITATION_SEND_ERR)
super(SendInvitationError, self).__init__()
class SendGreetingError(SendMailError):
def __init__(self):
self.message = _('Failed to send greeting')
self.message = _(astakos_messages.GREETING_SEND_ERR)
super(SendGreetingError, self).__init__()
class SendFeedbackError(SendMailError):
def __init__(self):
self.message = _('Failed to send feedback')
self.message = _(astakos_messages.FEEDBACK_SEND_ERR)
super(SendFeedbackError, self).__init__()
class ChangeEmailError(SendMailError):
def __init__(self):
self.message = _('Failed to send change email')
self.message = self.message = _(astakos_messages.CHANGE_EMAIL_SEND_ERR)
super(ChangeEmailError, self).__init__()
class SendNotificationError(SendMailError):
def __init__(self):
self.message = _('Failed to send notification email')
self.message = _(astakos_messages.NOTIFICATION_SEND_ERR)
super(SendNotificationError, self).__init__()
# Copyright 2011-2012 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. 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 GRNET S.A. ``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 GRNET S.A 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.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
ACCOUNT_AUTHENTICATION_FAILED = 'Cannot authenticate account.'
ACCOUNT_INACTIVE = 'Inactive account.'
ACCOUNT_ALREADY_ACTIVE = 'Account is already active.'
TOKEN_UNKNOWN = 'There is no user matching this token.'
INVITATION_SENT = 'Invitation sent to %(emails.'
PROFILE_UPDATED = 'Profile has been updated successfully.'
FEEDBACK_SENT = 'Feedback successfully sent.'
EMAIL_CHANGED = 'Account email has been changed successfully.'
EMAIL_CHANGE_REGISTERED = 'Change email request has been registered succefully. \
You are going to receive a verification email in the new address.'
OBJECT_CREATED = 'The %(verbose_names was created successfully.'
MEMBER_JOINED_GROUP = '%(realnames has been successfully joined the group.'
MEMBER_REMOVED = '%(realnames has been successfully removed from the group.'
BILLING_ERROR = 'Service response status: %(status)d'
LOGOUT_SUCCESS = 'You have successfully logged out.'
GENERIC_ERROR = 'Something wrong has happened. \
Please contact the administrators for more details.'
MAX_INVITATION_NUMBER_REACHED = 'There are no invitations left.'
GROUP_MAX_PARTICIPANT_NUMBER_REACHED = 'Group maximum participant number has been reached.'
NO_APPROVAL_TERMS = 'There are no approval terms.'
PENDING_EMAIL_CHANGE_REQUEST = 'There is already a pending change email request.'
OBJECT_CREATED_FAILED = 'The %(verbose_names creation failed: %(reasons.'
GROUP_JOIN_FAILURE = 'Failed to join group.'
GROUPKIND_UNKNOWN = 'There is no such a group kind'
NOT_MEMBER = 'User is not member of the group.'
NOT_OWNER = 'User is not a group owner.'
OWNER_CANNOT_LEAVE_GROUP = 'Owner cannot leave the group.'
# Field validation fields
REQUIRED_FIELD = 'This field is required.'
EMAIL_USED = 'This email address is already in use. Please supply a different email address.'
SHIBBOLETH_EMAIL_USED = 'This email is already associated with another shibboleth account.'
SHIBBOLETH_INACTIVE_ACC = 'This email is already associated with an inactive account. \
You need to wait to be activated before being able to switch to a shibboleth account.'
SIGN_TERMS = 'You have to agree with the terms.'
CAPTCHA_VALIDATION_ERR = 'You have not entered the correct words.'
SUSPENDED_LOCAL_ACC = 'Local login is not the current authentication method for this account.'
UNUSABLE_PASSWORD = 'This account has not a usable password.'
EMAIL_UNKNOWN = 'That e-mail address doesn\'t have an associated user account. \
Are you sure you\'ve registered?'
INVITATION_EMAIL_EXISTS = 'There is already invitation for this email.'
INVITATION_CONSUMED_ERR = 'Invitation is used.'
UNKNOWN_USERS = 'Unknown users: %s'
UNIQUE_EMAIL_IS_ACTIVE_CONSTRAIN_ERR = 'Another account with the same email & is_active combination found.'
INVALID_ACTIVATION_KEY = 'Invalid activation key.'
NEW_EMAIL_ADDR_RESERVED = 'The new email address is reserved.'
EMAIL_RESERVED = 'Email: %(email)s is reserved'
# Field help text
ADD_GROUP_MEMBERS_Q_HELP = 'Add comma separated user emails, eg. user1@user.com, user2@user.com'
ASTAKOSUSER_GROUPS_HELP = 'In addition to the permissions manually assigned, \
this user will also get all permissions granted to each group he/she is in.'
EMAIL_CHANGE_NEW_ADDR_HELP = 'Your old email address will be used until you verify your new one.'
EMAIL_SEND_ERR = 'Failed to send %s.'
ADMIN_NOTIFICATION_SEND_ERR = EMAIL_SEND_ERR % 'admin notification'
VERIFICATION_SEND_ERR = EMAIL_SEND_ERR % 'verification'
INVITATION_SEND_ERR = EMAIL_SEND_ERR % 'invitation'
GREETING_SEND_ERR = EMAIL_SEND_ERR % 'greeting'
FEEDBACK_SEND_ERR = EMAIL_SEND_ERR % 'feedback'
CHANGE_EMAIL_SEND_ERR = EMAIL_SEND_ERR % 'feedback'
NOTIFICATION_SEND_ERR = EMAIL_SEND_ERR % 'notification'
MISSING_NEXT_PARAMETER = 'No next parameter'
VERIFICATION_SENT = 'Verification sent.'
SWITCH_ACCOUNT_LINK_SENT = 'This email is already associated with another local account. \
To change this account to a shibboleth one follow the link in the verification email sent to %(emails. \
Otherwise just ignore it.'
NOTIFACATION_SENT = 'Your request for an account was successfully received and is now pending approval. \
You will be notified by email in the next few days. \
Thanks for your interest in ~okeanos! The GRNET team.'
REGISTRATION_COMPLETED = 'Registration completed. You can now login.'
\ No newline at end of file
......@@ -44,6 +44,7 @@ from collections import defaultdict
from django.db import models, IntegrityError
from django.contrib.auth.models import User, UserManager, Group, Permission
from django.utils.translation import ugettext as _
from django.db import transaction
from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models.signals import (pre_save, post_save, post_syncdb,
......@@ -63,6 +64,8 @@ from astakos.im.functions import send_invitation
from astakos.im.tasks import propagate_groupmembers_quota
from astakos.im.functions import send_invitation
import astakos.im.messages as astakos_messages
logger = logging.getLogger(__name__)
DEFAULT_CONTENT_TYPE = None
......@@ -228,14 +231,20 @@ class AstakosGroup(Group):
self.save()
quota_disturbed.send(sender=self, users=self.approved_members)
@transaction.commit_manually
def approve_member(self, person):
m, created = self.membership_set.get_or_create(person=person)
# update date_joined in any case
m.date_joined = datetime.now()
m.save()
try:
m.approve()
except:
transaction.rollback()
raise
else:
transaction.commit()
def disapprove_member(self, person):
self.membership_set.remove(person=person)
# def disapprove_member(self, person):
# self.membership_set.remove(person=person)
@property
def members(self):
......@@ -340,9 +349,7 @@ class AstakosUser(User):
astakos_groups = models.ManyToManyField(
AstakosGroup, verbose_name=_('agroups'), blank=True,
help_text=_("""In addition to the permissions manually assigned, this
user will also get all permissions granted to each group
he/she is in."""),
help_text=_(astakos_messages.ASTAKOSUSER_GROUPS_HELP),
through='Membership')
__has_signed_terms = False
......@@ -519,7 +526,7 @@ class AstakosUser(User):
q = q.filter(email=self.email)
q = q.filter(is_active=self.is_active)
if q.count() != 0:
raise ValidationError({'__all__': [_('Another account with the same email & is_active combination found.')]})
raise ValidationError({'__all__': [_(astakos_messages.UNIQUE_EMAIL_IS_ACTIVE_CONSTRAIN_ERR)]})
@property
def signed_terms(self):
......@@ -564,6 +571,8 @@ class Membership(models.Model):
return False
def approve(self):
if self.group.max_participants:
assert len(self.group.approved_members) + 1 <= self.group.max_participants
self.date_joined = datetime.now()
self.save()
quota_disturbed.send(sender=self, users=(self.person,))
......@@ -690,7 +699,7 @@ class EmailChangeManager(models.Manager):
except AstakosUser.DoesNotExist:
pass
else:
raise ValueError(_('The new email address is reserved.'))
raise ValueError(_(astakos_messages.NEW_EMAIL_ADDR_RESERVED))
# update user
user = AstakosUser.objects.get(pk=email_change.user_id)
user.email = email_change.new_email_address
......@@ -698,12 +707,12 @@ class EmailChangeManager(models.Manager):
email_change.delete()
return user
except EmailChange.DoesNotExist:
raise ValueError(_('Invalid activation key'))
raise ValueError(_(astakos_messages.INVALID_ACTIVATION_KEY))
class EmailChange(models.Model):
new_email_address = models.EmailField(_(u'new e-mail address'),
help_text=_(u'Your old email address will be used until you verify your new one.'))
help_text=_(astakos_messages.EMAIL_CHANGE_NEW_ADDR_HELP))
user = models.ForeignKey(
AstakosUser, unique=True, related_name='emailchange_user')
requested_at = models.DateTimeField(default=datetime.now())
......@@ -851,4 +860,4 @@ post_delete.connect(send_quota_disturbed, sender=Membership)
post_save.connect(send_quota_disturbed, sender=AstakosUserQuota)
post_delete.connect(send_quota_disturbed, sender=AstakosUserQuota)
post_save.connect(send_quota_disturbed, sender=AstakosGroupQuota)
post_delete.connect(send_quota_disturbed, sender=AstakosGroupQuota)
post_delete.connect(send_quota_disturbed, sender=AstakosGroupQuota)
\ No newline at end of file
......@@ -43,6 +43,8 @@ from astakos.im.views import requires_anonymous
from astakos.im.forms import LoginForm
from astakos.im.settings import RATELIMIT_RETRIES_ALLOWED
import astakos.im.messages as astakos_messages
from ratelimit.decorators import ratelimit
retries = RATELIMIT_RETRIES_ALLOWED - 1
......@@ -72,9 +74,9 @@ def login(request, on_failure='im/login.html'):
message = None
if not user:
message = _('Cannot authenticate account')
message = _(astakos_messages.ACCOUNT_AUTHENTICATION_FAILED)
elif not user.is_active:
message = _('Inactive account')
message = _(astakos_messages.ACCOUNT_INACTIVE)
if message:
messages.error(request, message)
return render_to_response(on_failure,
......
......@@ -45,6 +45,8 @@ from astakos.im.settings import COOKIE_NAME, COOKIE_DOMAIN
from astakos.im.util import set_cookie
from astakos.im.functions import login as auth_login, logout
import astakos.im.messages as astakos_messages
import logging
logger = logging.getLogger(__name__)
......@@ -62,7 +64,7 @@ def login(request):
"""
next = request.GET.get('next')
if not next:
return HttpResponseBadRequest(_('No next parameter'))
return HttpResponseBadRequest(_(astakos_messages.MISSING_NEXT_PARAMETER))
force = request.GET.get('force', None)
response = HttpResponse()
if force == '':
......
......@@ -43,6 +43,7 @@ from astakos.im.models import AstakosUser
from astakos.im.forms import LoginForm
from astakos.im.activation_backends import get_backend, SimpleBackend
import astakos.im.messages as astakos_messages
class Tokens:
# these are mapped by the Shibboleth SP software
......@@ -89,7 +90,7 @@ def login(request, backend=None, on_login_template='im/login.html',
request.GET.get('next'),
'renew' in request.GET)
else:
message = _('Inactive account')
message = _(astakos_messages.ACCOUNT_INACTIVE)
messages.error(request, message)
return render_response(on_login_template,
login_form=LoginForm(request=request),
......
......@@ -75,6 +75,8 @@
{% endif %}
{% endfor %}&nbsp;
</dd>
<dt>Max participants</dt>
<dd>{% if object.max_participants%}{{object.max_participants}}{% else %}&nbsp;{% endif %}</dd>
</dl>
</div>
......
{% extends "im/account_base.html" %}
{% load filters %}
{% block headjs %}
{{ block.super }}
<script src="{{ IM_STATIC_URL }}js/quotas.js"></script>
{% endblock %}
{% block page.body %}
<div class="projects">
<div class="maincol {% block innerpage.class %}{% endblock %}">
<form action="" method="post"
class="withlabels">{% csrf_token %}
<h2><span>CREATE {{ kind|upper }}</span></h2>
{% include "im/form_render.html" %}
<div class="form-row submit">
<input type="submit" class="submit altcol" value="SUBMIT" />
</div>
</form>
<form action="" method="post" class="withlabels quotas-form">{% csrf_token %}
<fieldset class="with-info">
<legend>
1. CREATE GROUP
<span class="info">
<em>more info</em>
<span>Help Text Help Text Help Text Help Text Help Text Help Text Help Text Help Text Help Text Help Text Help Text Help Text Help Text Help Text</span>
</span>
</legend>
{% include "im/form_render.html" %}
<!--
<div class="double-checks">
<label>Max users per group</label>
<div class="form-row">
<p class="clearfix">
<label for="members_unlimited">Unlimited</label>
<input type="checkbox" id="members_unlimited" name="members_unlimited" class="unlimited" checked="checked">
<span class="info">
<em>more info</em>
<span>Help Text Help Text Help Text Text Help Text Help Text</span>
</span>
</p>
</div>
<div class="form-row">
<p class="clearfix">
<label for="members_limited">Limited</label>
<input type="checkbox" id="members_limited" name="members_limited" class="limited">
<input type="text" id="members_uplimit" name="members_uplimit" />
</p>
</div>
</div>
-->
</fieldset>