Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added new and edit for organization #222

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions boxoffice/assets/js/views/org.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ const orgTemplate = `
<a class="boxoffice-button boxoffice-button-action btn-right" href="/admin/o/{{orgName}}/ic/new" data-navigate>
New item collection
</a>
<a class="boxoffice-button boxoffice-button-action btn-right btn-margin-right" href="/admin/o/{{orgName}}/edit">
Edit organization
</a>
</div>
{{#itemCollections:ic}}
<div class="box col-sm-6 col-xs-12" id="item-collection-{{ @index }}">
Expand Down
1 change: 1 addition & 0 deletions boxoffice/forms/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-

from .org import *
from .item_collection import *
from .item import *
from .price import *
Expand Down
19 changes: 1 addition & 18 deletions boxoffice/forms/item.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,16 @@
# -*- coding: utf-8 -*-

import json
from flask import request
from html5print import HTMLBeautifier
from baseframe import __
import baseframe.forms as forms
from baseframe.forms.sqlalchemy import QuerySelectField
from ..models import db, Category, ItemCollection
from .utils import format_json, validate_json

__all__ = ['ItemForm']


def format_json(data):
if request.method == 'GET':
return json.dumps(data, indent=4, sort_keys=True)
# `json.loads` doesn't raise an exception for "null"
# so assign a default value of `{}`
if not data or data == 'null':
return json.dumps({})
return data


def format_description(data):
if request.method == 'GET' and data:
return HTMLBeautifier.beautify(data.text, 8)
Expand Down Expand Up @@ -49,13 +39,6 @@ def format_description(data):
}


def validate_json(form, field):
try:
json.loads(field.data)
except ValueError:
raise forms.validators.StopValidation(__("Invalid JSON"))


class ItemForm(forms.Form):
title = forms.StringField(__("Item title"),
validators=[forms.validators.DataRequired(__("Please specify a title")),
Expand Down
41 changes: 41 additions & 0 deletions boxoffice/forms/org.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-

from boxoffice.models import db, Organization
from baseframe.forms.sqlalchemy import QuerySelectField
import baseframe.forms as forms
from coaster.utils import buid
from baseframe import __
from .utils import validate_json, format_json

__all__ = ['OrgForm', 'NewOrgForm']


DEFAULT_ORG_DETAILS = {
u'access_token': buid(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is declaring a fixed access token as a global.

u'address': u'',
u'cin': u'',
u'logo': u'',
u'pan': u'',
u'refund_policy': u'',
u'support_email': u'',
u'ticket_faq': u'',
u'website': u''
}


class OrgForm(forms.Form):
contact_email = forms.EmailField(__("Contact email"),
validators=[forms.validators.DataRequired(__("Please enter an email address")), forms.validators.Length(min=5, max=80)])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use an email validator instead of a length validator.

details = forms.TextAreaField(__("Details"), filters=[format_json],
validators=[validate_json], default=DEFAULT_ORG_DETAILS)
invoicer = QuerySelectField(__("Parent organization"), get_label='title',
validators=[forms.validators.DataRequired(__("Please select a parent organization"))])

def set_queries(self):
self.invoicer.query = Organization.query.filter(
Organization.invoicer == None).options(db.load_only('id', 'title'))


class NewOrgForm(OrgForm):
organization = forms.RadioField(u"Organization", validators=[forms.validators.DataRequired("Select an organization")],
description=u"Select the organization you’d like to setup Boxoffice for")
23 changes: 23 additions & 0 deletions boxoffice/forms/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-

import json
from flask import request
import baseframe.forms as forms
from baseframe import __


def validate_json(form, field):
try:
json.loads(field.data)
except ValueError:
raise forms.validators.StopValidation(__("Invalid JSON"))


def format_json(data):
if request.method == 'GET':
return json.dumps(data, indent=4, sort_keys=True)
# `json.loads` doesn't raise an exception for "null"
# so assign a default value of `{}`
if not data or data == 'null':
return json.dumps({})
return data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it doesn't format JSON if the request is not a GET? That doesn't make sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The formatting with indents is only to make it easy for the admin to edit it. simplejson fails when the formatted version is supplied via populate_obj.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the "return data" bit. It's returning the Python data object if the request is not a GET? Why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's because json.dumps(data) will result in a JSON string, which errors out when I attempt to store it in the database. Since it expects a dict, I just return the data dict.

1 change: 0 additions & 1 deletion boxoffice/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ class Organization(ProfileBase, db.Model):
invoicer = db.relationship('Organization', remote_side='Organization.id',
backref=db.backref('subsidiaries', cascade='all, delete-orphan', lazy='dynamic'))


def permissions(self, user, inherited=None):
# import IPython; IPython.embed();
perms = super(Organization, self).permissions(user, inherited)
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion boxoffice/static/build/manifest.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"assets":{"main_order":"main_order.be0a8f034a0fd223228e.js","main_invoice":"main_invoice.be0a8f034a0fd223228e.js","main_admin":"main_admin.be0a8f034a0fd223228e.js"}}
{"assets":{"main_order":"main_order.77834e515b9b2330ff9c.js","main_invoice":"main_invoice.77834e515b9b2330ff9c.js","main_admin":"main_admin.77834e515b9b2330ff9c.js"}}
2 changes: 1 addition & 1 deletion boxoffice/views/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-

from . import admin, item_collection, order, participant, login, admin_item_collection, admin_order, admin_discount, admin_report, admin_item, admin_category
from . import admin, org, item_collection, order, participant, login, admin_item_collection, admin_order, admin_discount, admin_report, admin_item, admin_category
77 changes: 77 additions & 0 deletions boxoffice/views/org.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-

from flask import g, Markup, request, flash, url_for, redirect
from coaster.views import load_models
from baseframe import _
from baseframe.forms import render_message, render_redirect, render_form
from ..models import db, Organization
from ..forms import OrgForm, NewOrgForm
from .. import app, lastuser


@lastuser.requires_permission('siteadmin')
@lastuser.requires_login
@app.route('/admin/o/new', methods=['GET', 'POST'])
def new_org():
# Step 1: Get a list of organizations this user owns
existing = Organization.query.filter(Organization.userid.in_(g.user.organizations_owned_ids())).all()
existing_ids = [e.userid for e in existing]
# Step 2: Prune list to organizations without a profile
new_orgs = []
for user_org in g.user.organizations_owned():
if user_org['userid'] not in existing_ids:
new_orgs.append((user_org['userid'], user_org['title']))
if not new_orgs:
return render_message(
title=_(u"No organizations found"),
message=Markup(_(u"You do not have any organizations that are not setup on Boxoffice. "
u'Would you like to <a href="{link}">create a new organization</a>?').format(
link=lastuser.endpoint_url('/organizations/new'))))
eligible_orgs = []
for orgid, title in new_orgs:
eligible_orgs.append((orgid, title))
if not eligible_orgs:
return render_message(
title=_(u"No organizations available"),
message=_(u"To setup Boxoffice for an organization, you must be the owner of the organization."))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the logic used in Funnel's /new handler. This code looks fragile.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Damn, then that needs to be tossed as well. We can live with this for now, knowing that both places will need to be changed after hasgeek/lastuser#232.

# Step 3: Ask user to select organization
form = NewOrgForm()
form.organization.choices = eligible_orgs
if request.method == 'GET':
form.organization.data = new_orgs[0][0]
if form.validate_on_submit():
# Step 4: Make a profile
user_org = [user_org for user_org in g.user.organizations_owned() if user_org['userid'] == form.organization.data][0]
organization = Organization(name=user_org['name'], title=user_org['title'], userid=user_org['userid'])
form.populate_obj(organization)
db.session.add(organization)
db.session.commit()
flash(_(u"Created an organization for {org}").format(org=organization.title), "success")
return redirect(url_for('org', org=organization.name), code=303)
return render_form(
form=form,
title=_(u"Setup Boxoffice for your organization..."),
submit="Next",
formid="org_new",
cancel_url=url_for('index'),
ajax=False)


@app.route('/admin/o/<name>/edit', methods=['GET', 'POST'])
@load_models(
(Organization, {'name': 'name'}, 'org'),
permission='org_admin')
def edit_org(org):
form = OrgForm(obj=org)
if form.validate_on_submit():
form.populate_obj(org)
db.session.commit()
flash(_("Your changes have been saved"), 'info')
return redirect(url_for('org', org=org.name), code=303)
return render_form(
form=form,
title=_("Edit organization settings"),
submit=_("Save changes"),
cancel_url='/admin/o/{{org}}'.format(org=org.name),
ajax=False)