Browse Source

Add slug name for organisations.

develop
Sam Black 4 years ago
parent
commit
1171f43715
  1. 40
      migrations/versions/056e315c9f5e_.py
  2. 9
      vowel/forms/organisation.py
  3. 1
      vowel/models/organisations.py
  4. 1
      vowel/templates/landing/signup.html
  5. 10
      vowel/templates/organisation/edit.html
  6. 28
      vowel/utils/organisation.py
  7. 122
      vowel/views/organisation.py
  8. 21
      vowel/web.py

40
migrations/versions/056e315c9f5e_.py

@ -0,0 +1,40 @@
"""
Add organisation slug data to construct URLs and subdomains.
Revision ID: 056e315c9f5e
Revises: c5dd2bd49172
Create Date: 2018-02-12 16:52:13.353669
"""
# revision identifiers, used by Alembic.
revision = '056e315c9f5e'
down_revision = 'c5dd2bd49172'
from alembic import op
import sqlalchemy as sa
from sqlalchemy.orm import sessionmaker
from vowel.models.organisations import Organisation
def upgrade():
bind = op.get_bind()
session = sessionmaker()(bind=bind)
# Slug is not nullable, but we make it that way after populating some data
op.add_column('organisation', sa.Column('slug', sa.String(length=255)))
op.create_unique_constraint(None, 'organisation', ['slug'])
# Populate some default data in Organisations.slug
# by taking the organisation name and just removing the spaces
for org in session.query(Organisation):
org.slug = org.name.lower().replace(" ", "")
session.commit()
op.alter_column("organisation", "slug", nullable=False)
def downgrade():
op.drop_constraint(None, 'organisation', type_='unique')
op.drop_column('organisation', 'slug')

9
vowel/forms/organisation.py

@ -28,6 +28,7 @@ from wtforms import StringField
from wtforms import TextAreaField
from wtforms.validators import DataRequired
from wtforms.validators import InputRequired
from wtforms.validators import Length
from vowel.forms.utils import HTMLInputField
from vowel.models.organisations import contact_choices
@ -53,6 +54,10 @@ class OrganisationForm(FlaskForm):
contact_information = FieldList(FormField(OrganisationContactForm))
name = StringField(validators=[DataRequired(), InputRequired()])
slug = StringField(
"Short name",
validators=[DataRequired(), InputRequired(), Length(min=5)],
description="Shortened company name to use for your site address")
description = HTMLInputField()
address = TextAreaField()
@ -66,6 +71,10 @@ class OrganisationForm(FlaskForm):
super().__init__(*args, **kwargs)
self.company_number.flags.placeholder = True
self.slug.flags.placeholder = True
self.slug.flags.add_hidden = True
self.slug.flags.add_hidden = True
class NewOrganisationForm(OrganisationForm):
"""

1
vowel/models/organisations.py

@ -67,6 +67,7 @@ class Organisation(db.Model):
server_default=sql_text("false"), nullable=False)
name = db.Column(db.String(255), unique=True, nullable=False)
slug = db.Column(db.String(255), unique=True, nullable=False)
description = db.Column(db.Text())
address = db.Column(db.Text())

1
vowel/templates/landing/signup.html

@ -29,6 +29,7 @@
<h4>Your company's information</h4>
{{ render_field(form.name) }}
{{ render_field(form.slug, extra_id="organisationSlugName") }}
{{ render_field(form.address, rows=6) }}
{{ render_field(form.submit, False, btn_class="primary") }}

10
vowel/templates/organisation/edit.html

@ -54,14 +54,18 @@
</div>
{% endif %}
<form name="organisation_edit"
action="{{ post_url }}" method="post">
<form name="organisation_edit" action="{{ post_url }}" method="post">
<div data-uk-grid>
<div class="uk-width-4-5@l">
{{ form.hidden_tag() }}
{{ render_field(form.name) }}
{% if current_user.has_role("Admin") %}
{{ render_field(form.name) }}
{{ render_field(form.slug, extra_id="organisationSlugName") }}
{{ render_field(form.enabled) }}
{% else %}
{# This isn't editable by the organisation owner #}
{{ render_field(form.name, static=True) }}
{{ render_field(form.slug, extra_id="organisationSlugName", static=True) }}
{% endif %}
{{ render_field(form.description, extra_id="description") }}
{{ render_field(form.address, rows=6) }}

28
vowel/utils/organisation.py

@ -37,6 +37,10 @@ from vowel.utils.queue import rq_manager
ORGANISATION_TEMPLATES = "organisation/email"
class OrganisationNotFound(Exception):
pass
@rq_manager.job()
def _mail_organisation(email, organisation_id):
"""
@ -137,3 +141,27 @@ def organisation_from_request(view_args):
return assessment.module.organisation_id
return None
def organisation_from_id_or_slug(org_id=None, org_slug=None):
"""
Return the organisation from the provided ID or slug.
:param org_id: organisation ID
:type org_id: int
:param org_slug: organisation slug name
:type org_slug: str
:return: the corresponding organisation
:rtype: vowel.models.organisation.Organisation
"""
if org_id:
org = Organisation.query.get(org_id)
elif org_slug:
org = Organisation.query.filter_by(slug=org_slug).first()
else:
raise OrganisationNotFound("No organisation provided")
if org:
return org
else:
raise OrganisationNotFound("No organisation found")

122
vowel/views/organisation.py

@ -19,6 +19,7 @@
import arrow
from flask import Blueprint
from flask import abort
from flask import flash
from flask import redirect
from flask import render_template
@ -44,6 +45,8 @@ from vowel.models.tokens import TokenPurchase
import vowel.utils.branding
from vowel.utils import cache
from vowel.utils.auth import is_staff_member
from vowel.utils.organisation import OrganisationNotFound
from vowel.utils.organisation import organisation_from_id_or_slug
from vowel.utils.tokens import update_token_types
@ -51,17 +54,23 @@ organisation = Blueprint("organisations", __name__)
@organisation.route("/<int:organisation_id>/")
@organisation.route("/<organisation_slug>/")
@login_required
def view(organisation_id):
def view(organisation_id=None, organisation_slug=None):
"""
View lesson presentation using reveal.js
View organisation information
:param organisation_id: organisation to view
:param organisation_id: organisation to view, selected by ID
:type organisation_id: int
:param organisation_slug: organisation to view, selected by slug name
:type organisation_slug: str
:return: HTML output
:rtype: jinja2.Template
"""
org = Organisation.query.filter_by(id=organisation_id).first_or_404()
try:
org = organisation_from_id_or_slug(organisation_id, organisation_slug)
except OrganisationNotFound:
raise abort(404)
return render_template("organisation/view.html", organisation=org)
@ -152,44 +161,60 @@ def _edit_organisation(organisation_id=None):
@organisation.route("/<int:organisation_id>/edit/", methods=["GET", "POST"])
@organisation.route("/<organisation_slug>/edit/", methods=["GET", "POST"])
@login_required
@is_staff_member()
def edit(organisation_id):
def edit(organisation_id=None, organisation_slug=None):
"""
Edit organisation.
:param organisation_id: organisation to view
:param organisation_id: organisation to edit, by ID
:type organisation_id: int
:param organisation_slug: organisation to edit, by slug name
:type organisation_slug: str
:return: HTML output
:rtype: jinja2.Template
"""
return _edit_organisation(organisation_id)
try:
org = organisation_from_id_or_slug(organisation_id, organisation_slug)
except OrganisationNotFound:
raise abort(404)
return _edit_organisation(org.id)
@organisation.route("/<int:organisation_id>/edit/branding/",
methods=["GET", "POST"])
@organisation.route("/<organisation_slug>/edit/branding/",
methods=["GET", "POST"])
@login_required
@is_staff_member()
def branding(organisation_id):
def branding(organisation_id=None, organisation_slug=None):
"""
Edit organisation branding.
:param organisation_id: organisation branding to edit
:param organisation_id: organisation branding to edit, by ID
:type organisation_id: int
:param organisation_slug: organisation branding to edit, by slug name
:type organisation_slug: str
:return: HTML output
:rtype: jinja2.Template
"""
organisation = Organisation.query.get_or_404(organisation_id)
try:
org = organisation_from_id_or_slug(organisation_id, organisation_slug)
except OrganisationNotFound:
raise abort(404)
org_branding = OrganisationBranding.query.filter_by(
organisation_id=organisation_id).first()
organisation_id=org.id).first()
if not org_branding:
org_branding = OrganisationBranding(organisation_id=organisation_id)
org_branding = OrganisationBranding(organisation_id=org.id)
form = OrganisationBrandingForm(obj=org_branding)
if org_branding.logo_id:
logo_url = url_for("resources.file_view",
organisation_id=organisation_id,
organisation_id=org.id,
purpose=org_branding.logo.purpose,
document_id=org_branding.logo_id,
_external=True)
@ -203,7 +228,7 @@ def branding(organisation_id):
except ValueError:
flash("Logo could not be found", "danger")
return redirect(url_for("organisations.branding",
organisation_id=organisation_id))
organisation_id=org.id))
resource = Resource.query.get(logo_res_id)
if resource:
@ -211,7 +236,7 @@ def branding(organisation_id):
else:
flash("Logo could not be found", "danger")
return redirect(url_for("organisations.branding",
organisation_id=organisation_id))
organisation_id=org.id))
elif form.logo_delete.data:
org_branding.logo_id = None
@ -221,15 +246,14 @@ def branding(organisation_id):
db.session.commit()
# Drop the cached branding info as it has changed
cache.delete_memoized(vowel.utils.branding._branding, organisation_id)
cache.delete_memoized(vowel.utils.branding._branding, org.id)
flash("Organisation branding saved", "success")
return redirect(url_for("organisations.branding",
organisation_id=organisation_id))
organisation_id=org.id))
return render_template("organisation/branding.html",
organisation=organisation, form=form,
logo_url=logo_url)
organisation=org, form=form, logo_url=logo_url)
@organisation.route("/new/", methods=["GET", "POST"])
@ -246,21 +270,28 @@ def new():
@organisation.route("/<int:organisation_id>/tokens/")
@organisation.route("/<organisation_slug>/tokens/")
@login_required
@is_staff_member()
def tokens(organisation_id):
def tokens(organisation_id=None, organisation_slug=None):
"""
Tokens for this organisation.
:param organisation_id: organisation identification
:type organisation_id: int
:param organisation_slug: organisation identification by slug
:type organisation_slug: str
:return: HTML output
:rtype: jinja2.Template
"""
org = Organisation.query.get_or_404(organisation_id)
update_token_types(organisation_id)
try:
org = organisation_from_id_or_slug(organisation_id, organisation_slug)
except OrganisationNotFound:
raise abort(404)
update_token_types(org.id)
org_tokens = Token.query.filter_by(organisation_id=organisation_id).all()
org_tokens = Token.query.filter_by(organisation_id=org.id).all()
return render_template("organisation/tokens.html", organisation=org,
tokens=org_tokens)
@ -268,20 +299,27 @@ def tokens(organisation_id):
@organisation.route("/<int:organisation_id>/tokens/purchase/<int:token_id>/",
methods=["GET", "POST"])
@organisation.route("/<organisation_slug>/tokens/purchase/<int:token_id>/",
methods=["GET", "POST"])
@login_required
@is_staff_member()
def token_purchase(organisation_id, token_id):
def token_purchase(token_id, organisation_id=None, organisation_slug=None):
"""
Purchase tokens for this organisation.
:param organisation_id: organisation identification
:type organisation_id: int
:param token_id: token identification
:type token_id: int
:param organisation_id: organisation identification, by ID
:type organisation_id: int
:param organisation_slug: organisation identification, by slug
:type organisation_slug: str
:return: HTML output
:rtype: jinja2.Template
"""
org = Organisation.query.get_or_404(organisation_id)
try:
org = organisation_from_id_or_slug(organisation_id, organisation_slug)
except OrganisationNotFound:
raise abort(404)
token = Token.query.get_or_404(token_id)
token_form = TokenPurchaseForm()
@ -315,7 +353,7 @@ def token_purchase(organisation_id, token_id):
flash("Tokens purchased", "success")
return redirect(url_for("organisations.tokens",
organisation_id=organisation_id))
organisation_id=org.id))
else:
token_form.currency.data = "GBP"
@ -324,35 +362,47 @@ def token_purchase(organisation_id, token_id):
@organisation.route("/<int:organisation_id>/tokens/history/")
@organisation.route("/<organisation_slug>/tokens/history/")
@login_required
@is_staff_member()
def token_history(organisation_id):
def token_history(organisation_id=None, organisation_slug=None):
"""
Token purchase history
:param organisation_id: organisation
:param organisation_id: organisation by ID
:type organisation_id: int
:param organisation_slug: organisation by slug
:type organisation_slug: str
:return: HTML output
:rtype: jinja2.Template
"""
org = Organisation.query.get_or_404(organisation_id)
update_token_types(organisation_id)
org_tokens = Token.query.filter_by(organisation_id=organisation_id).all()
try:
org = organisation_from_id_or_slug(organisation_id, organisation_slug)
except OrganisationNotFound:
raise abort(404)
update_token_types(org.id)
org_tokens = Token.query.filter_by(organisation_id=org.id).all()
return render_template("organisation/tokens_list.html", organisation=org,
tokens=org_tokens)
@organisation.route("/<int:organisation_id>/tokens/payment_info/")
@organisation.route("/<organisation_slug>/tokens/payment_info/")
@login_required
@is_staff_member()
def token_payment_info(organisation_id):
def token_payment_info(organisation_id=None, organisation_slug=None):
"""
Token payment methods
:param organisation_id: organisation
:param organisation_id: organisation by ID
:type organisation_id: int
:param organisation_slug: organisation by slug
:type organisation_slug: str
:return: HTML output
:rtype: jinja2.Template
"""
pass
try:
org = organisation_from_id_or_slug(organisation_id, organisation_slug)
except OrganisationNotFound:
raise abort(404)

21
vowel/web.py

@ -273,17 +273,20 @@ def register_blueprints(app):
:param app: Flask application
:type app: flask.Flask
"""
# These apps register their url_prefix
app.register_blueprint(users)
app.register_blueprint(organisation)
app.register_blueprint(dashboard)
app.register_blueprint(courses)
app.register_blueprint(modules)
app.register_blueprint(lessons)
app.register_blueprint(assessment)
app.register_blueprint(analytics)
app.register_blueprint(resources)
app.register_blueprint(presentation)
app.register_blueprint(annotations)
# TODO: Register site admin app
app.register_blueprint(organisation, url_prefix="/organisation")
app.register_blueprint(courses, url_prefix="/course")
#app.register_blueprint(modules)
#app.register_blueprint(lessons)
#app.register_blueprint(assessment)
app.register_blueprint(analytics, url_prefix="/analytics")
#app.register_blueprint(resources)
#app.register_blueprint(presentation)
#app.register_blueprint(annotations)
if app.config.get("VOWEL_COMMERCIAL"):
app.register_blueprint(landing)

Loading…
Cancel
Save