From 85c3efb482e9432ff12ebfd086c8920be9896986 Mon Sep 17 00:00:00 2001 From: Mike Milkin Date: Sun, 20 Jul 2014 16:30:53 -0400 Subject: [PATCH 01/17] adding group --- requirements.txt | 4 + studygroup/application.py | 1 + studygroup/exceptions.py | 17 ++++- studygroup/forms.py | 45 +++++++++-- studygroup/models.py | 11 +++ studygroup/templates/show_group.html | 21 ++++++ studygroup/views.py | 33 ++++++++- tests/test_membership.py | 107 +++++++++++++++++++++++++++ 8 files changed, 226 insertions(+), 13 deletions(-) create mode 100644 tests/test_membership.py diff --git a/requirements.txt b/requirements.txt index 5936510..050ac7e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,6 +16,10 @@ itsdangerous==0.23 oauthlib==0.6.1 psycopg2==2.5.2 wsgiref==0.1.2 +lxml==3.3.5 +requests==2.3.0 +cssselect==0.9.1 +mock==1.0.1 # Only for testing (separate out?) Flask-Testing==0.4 diff --git a/studygroup/application.py b/studygroup/application.py index d2a87d7..800c472 100644 --- a/studygroup/application.py +++ b/studygroup/application.py @@ -11,6 +11,7 @@ oauth = OAuth() migrate = Migrate() + def create_app(debug=True): from views import studygroup diff --git a/studygroup/exceptions.py b/studygroup/exceptions.py index c306f13..7f97a5b 100644 --- a/studygroup/exceptions.py +++ b/studygroup/exceptions.py @@ -1,10 +1,21 @@ - class ModelNotFoundException(Exception): pass -class GroupFullException(Exception): +class FormValidationException(Exception): + pass + +class UnAuthorizedException(FormValidationException): + def __init__(self, member_id): + message = "UnAuthorized action for member with Id: %s " % (member_id) + super(UnAuthorizedException, self).__init__(message) + +class MembershipException(FormValidationException): + def __init__(self, member_id, group_id): + message = "Member {%s} is already part of group: {%s} " % (member_id, group_id) + super(MembershipException, self).__init__(message) +class GroupFullException(FormValidationException): def __init__(self, group): message = "Group: %s Id: %s" % (group.name, group.id) - super(GroupFullException, self).__init__(message) \ No newline at end of file + super(GroupFullException, self).__init__(message) diff --git a/studygroup/forms.py b/studygroup/forms.py index d491683..2aff1ed 100644 --- a/studygroup/forms.py +++ b/studygroup/forms.py @@ -1,12 +1,12 @@ from flask import g from flask_wtf import Form -from wtforms import TextField, TextAreaField, IntegerField -from wtforms.validators import DataRequired +from wtforms import TextField, TextAreaField, IntegerField, ValidationError +from wtforms.validators import DataRequired, Required from wtforms.widgets import HiddenInput from .application import db from .models import Group, Membership, ROLE_GROUP_LEADER -from .exceptions import GroupFullException +from .exceptions import GroupFullException, UnAuthorizedException, MembershipException class GroupForm(Form): @@ -35,17 +35,50 @@ def save(self): class MembershipForm(Form): - user_id = IntegerField(widget=HiddenInput(), validators=[DataRequired()]) group_id = IntegerField(widget=HiddenInput(), validators=[DataRequired()]) + def __init__(self, user_id, *args, **kwargs): + self.user_id = user_id + super(MembershipForm, self).__init__(*args, **kwargs) + + def validate_group_id(form, field): + user_id = form._get_user() + group = Group.by_id_with_memberships(field.data) + form._validate_full_group(group) + form._validate_existing_member(group.id, user_id) + + def _validate_existing_member(self, group_id, user_id): + member = Membership.by_group_and_user_ids(group_id, user_id) + if member: + raise ValidationError(u'This member is already in this group') + + def _validate_full_group(self, group): + if group.is_full(): + raise ValidationError(u'This group is full') + + def _get_user(self): + user_id = getattr(self, 'user_id', None) + + if user_id is None: + raise ValidationError(u'Invalid user') + return user_id + def save(self): group = Group.by_id_with_memberships(self.group_id.data) + if group.is_full(): raise GroupFullException(group) + user_id = self._get_user() + + member = Membership.by_group_and_user_ids(group.id, user_id) + + if member: + raise MembershipException(group.id, user_id) + membership = Membership( - user_id=self.user_id.data, - group_id=self.group_id.data + user_id=user_id, + group_id=group.id ) db.session.add(membership) diff --git a/studygroup/models.py b/studygroup/models.py index 63cdfa8..4e6bf53 100644 --- a/studygroup/models.py +++ b/studygroup/models.py @@ -1,6 +1,7 @@ """ Data models for StudyGroups """ +from sqlalchemy import UniqueConstraint from .application import db import settings @@ -23,6 +24,16 @@ class Membership(db.Model): group_id = db.Column(db.Integer, db.ForeignKey('group.id')) role = db.Column(db.Integer, nullable=False, default=ROLE_MEMBER) + __table_args__ = ( + UniqueConstraint('user_id', 'group_id', name='_group_user_uc'), + ) + + @classmethod + def by_group_and_user_ids(cls, group_id, user_id): + return Membership.query.filter_by( + group_id=group_id, + user_id=user_id + ) class Group(db.Model): id = db.Column(db.Integer, primary_key=True) diff --git a/studygroup/templates/show_group.html b/studygroup/templates/show_group.html index d2babc8..b0652e8 100644 --- a/studygroup/templates/show_group.html +++ b/studygroup/templates/show_group.html @@ -1,4 +1,5 @@ {% extends "bootstrap/base.html" %} +{% import "bootstrap/wtf.html" as wtf %} {% block title %}Studygroup | Groups{% endblock %} {% block styles %} @@ -28,6 +29,26 @@

{{ g.group.name }}

{{ g.group.description }}
+
+

Members: {{ g.group.memberships | length }}

+
+ {% if form.errors %} + + {% endif %} + +
+
+ {{ form.hidden_tag() }} + {{ wtf.form_errors(form, hiddens="only") }} + +
+