Skip to content

Commit

Permalink
Locations are now board-specific. Fixes #333.
Browse files Browse the repository at this point in the history
  • Loading branch information
jace committed May 3, 2016
1 parent 127a386 commit 2d675b2
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 18 deletions.
35 changes: 35 additions & 0 deletions alembic/versions/da1dfcda8b3_location_scoped_to_board.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Location scoped to board
Revision ID: da1dfcda8b3
Revises: d19802512a1
Create Date: 2016-05-03 11:06:14.239742
"""

# revision identifiers, used by Alembic.
revision = 'da1dfcda8b3'
down_revision = 'd19802512a1'

from alembic import op
import sqlalchemy as sa


def upgrade():
op.add_column('location', sa.Column('board_id', sa.Integer(), nullable=False, server_default=sa.text('1')))
op.alter_column('location', 'board_id', server_default=None)
op.create_index(op.f('ix_location_board_id'), 'location', ['board_id'], unique=False)
op.drop_constraint(u'location_name_key', 'location', type_='unique')
op.create_unique_constraint('location_board_id_name_key', 'location', ['board_id', 'name'])
op.create_foreign_key('location_board_id_fkey', 'location', 'board', ['board_id'], ['id'])
op.drop_constraint('location_pkey', 'location', type_='primary')
op.create_primary_key('location_pkey', 'location', ['id', 'board_id'])


def downgrade():
op.drop_constraint('location_pkey', 'location', type_='primary')
op.create_primary_key('location_pkey', 'location', ['id'])
op.drop_constraint('location_board_id_fkey', 'location', type_='foreignkey')
op.drop_constraint('location_board_id_name_key', 'location', type_='unique')
op.create_unique_constraint('location_name_key', 'location', ['name'])
op.drop_index(op.f('ix_location_board_id'), table_name='location')
op.drop_column('location', 'board_id')
2 changes: 2 additions & 0 deletions hasjob/forms/all.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,6 @@ class NewLocationForm(forms.Form):


class EditLocationForm(forms.Form):
title = forms.StringField(__("Page title"),
validators=[forms.validators.DataRequired(__(u"This location needs a name"))])
description = forms.TinyMce4Field(__("Description"), content_css=content_css)
23 changes: 15 additions & 8 deletions hasjob/models/location.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
# -*- coding: utf-8 -*-

from . import db, BaseNameMixin
from . import db, BaseScopedNameMixin
from flask import url_for
from .board import Board

__all__ = ['Location']


class Location(BaseNameMixin, db.Model):
class Location(BaseScopedNameMixin, db.Model):
"""
A location where jobs are listed, using geonameid for primary key.
A location where jobs are listed, using geonameid for primary key. Scoped to a board
"""
__tablename__ = 'location'
id = db.Column(db.Integer, primary_key=True, autoincrement=False)
geonameid = db.synonym('id')
board_id = db.Column(None, db.ForeignKey('board.id'), nullable=False, primary_key=True, index=True)
parent = db.synonym('board_id')
board = db.relationship(Board, backref=db.backref('locations', lazy='dynamic', cascade='all, delete-orphan'))

#: Landing page description
description = db.Column(db.UnicodeText, nullable=True)

def url_for(self, action='view', _external=False, **kwargs):
__table_args__ = (db.UniqueConstraint('board_id', 'name'),)

def url_for(self, action='view', **kwargs):
subdomain = self.board.name if self.board.not_root else None
if action == 'view':
return url_for('browse_by_location', location=self.name, _external=_external, **kwargs)
return url_for('browse_by_location', location=self.name, subdomain=subdomain, **kwargs)
elif action == 'edit':
return url_for('location_edit', name=self.name, _external=_external, **kwargs)
return url_for('location_edit', name=self.name, subdomain=subdomain, **kwargs)

@classmethod
def get(cls, name):
return cls.query.filter_by(name=name).one_or_none()
def get(cls, name, board):
return cls.query.filter_by(name=name, board=board).one_or_none()
4 changes: 3 additions & 1 deletion hasjob/views/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -685,14 +685,16 @@ def sitemap(key):
sitemapxml = '<?xml version="1.0" encoding="UTF-8"?>\n'\
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
# Add featured boards to sitemap
board_ids = []
for board in Board.query.filter_by(featured=True).all():
board_ids.append(board.id)
sitemapxml += ' <url>\n'\
' <loc>%s</loc>\n' % board.url_for(_external=True) + \
' <lastmod>%s</lastmod>\n' % (board.updated_at.isoformat() + 'Z') + \
' <changefreq>monthly</changefreq>\n'\
' </url>\n'
# Add locations to sitemap
for item in Location.query.all():
for item in Location.query.filter(Location.board_id.in_(board_ids)).all():
sitemapxml += ' <url>\n'\
' <loc>%s</loc>\n' % item.url_for(_external=True) + \
' <lastmod>%s</lastmod>\n' % (item.updated_at.isoformat() + 'Z') + \
Expand Down
38 changes: 29 additions & 9 deletions hasjob/views/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,25 @@

from collections import OrderedDict
from datetime import datetime
from flask import redirect, abort
from baseframe.forms import render_form
from flask import g, redirect, abort, url_for
from baseframe import _
from baseframe.forms import render_form, render_delete_sqla
from ..models import db, agelimit, Location, JobLocation, JobPost, POSTSTATUS
from ..forms import NewLocationForm, EditLocationForm
from .. import app, lastuser
from .helper import location_geodata


@app.route('/in/new', methods=['GET', 'POST'], subdomain='<subdomain>')
@app.route('/in/new', methods=['GET', 'POST'])
@lastuser.requires_permission('siteadmin')
def location_new():
if not (lastuser.has_permission('siteadmin') or (g.board and g.board.owner_is(g.user))):
abort(403)
now = datetime.utcnow()
geonames = OrderedDict([(r.geonameid, None) for r in
db.session.query(JobLocation.geonameid, db.func.count(JobLocation.geonameid).label('count')).join(
JobPost).filter(JobPost.status.in_(POSTSTATUS.LISTED), JobPost.datetime > now - agelimit,
~JobLocation.geonameid.in_(db.session.query(Location.id))
~JobLocation.geonameid.in_(db.session.query(Location.id).filter(Location.board == g.board))
).group_by(JobLocation.geonameid).order_by(db.text('count DESC')).limit(100)])
data = location_geodata(geonames.keys())
for row in data.values():
Expand All @@ -30,18 +33,20 @@ def location_new():
geonameid, name = form.geoname.data.split('/', 1)
geonameid = int(geonameid)
title = geonames[geonameid]['use_title']
location = Location(id=geonameid, name=name, title=title)
location = Location(id=geonameid, board=g.board, name=name, title=title)
db.session.add(location)
db.session.commit()
return redirect(location.url_for('edit'), code=303)

return render_form(form=form, title="Add a location")
return render_form(form=form, title=_("Add a location"))


@app.route('/in/<name>/edit', methods=['GET', 'POST'], subdomain='<subdomain>')
@app.route('/in/<name>/edit', methods=['GET', 'POST'])
@lastuser.requires_permission('siteadmin')
def location_edit(name):
location = Location.get(name)
if not (lastuser.has_permission('siteadmin') or (g.board and g.board.owner_is(g.user))):
abort(403)
location = Location.get(name, g.board)
if not location:
abort(404)

Expand All @@ -50,4 +55,19 @@ def location_edit(name):
form.populate_obj(location)
db.session.commit()
return redirect(location.url_for(), code=303)
return render_form(form=form, title="Edit location")
return render_form(form=form, title=_("Edit location"))


@app.route('/in/<name>/delete', methods=['GET', 'POST'], subdomain='<subdomain>')
@app.route('/in/<name>/delete', methods=['GET', 'POST'])
def location_delete(name):
if not (lastuser.has_permission('siteadmin') or (g.board and g.board.owner_is(g.user))):
abort(403)
location = Location.get(name, g.board)
if not location:
abort(404)

return render_delete_sqla(location, db, title=_("Confirm delete"),
message=_(u"Delete location ‘{title}’?").format(title=location.title),
success=_(u"You have deleted location ‘{title}’.").format(title=location.title),
next=url_for('index'), cancel_url=location.url_for())

0 comments on commit 2d675b2

Please sign in to comment.