add versioning

This commit is contained in:
Edward Betts 2017-04-07 15:43:46 +01:00
parent 956dbadabd
commit 2f37577aaa
5 changed files with 90 additions and 11 deletions

View file

@ -5,15 +5,19 @@ from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, ForeignKey from sqlalchemy import Column, ForeignKey
from sqlalchemy.types import String, Unicode, Integer, DateTime, Boolean, UnicodeText, Enum from sqlalchemy.types import String, Unicode, Integer, DateTime, Boolean, UnicodeText, Enum
from sqlalchemy import func from sqlalchemy import func
from sqlalchemy.orm import relationship, validates, synonym from sqlalchemy.orm import relationship, validates, synonym, configure_mappers
from sqlalchemy.sql import exists from sqlalchemy.sql import exists
from flask_login import UserMixin from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.security import generate_password_hash, check_password_hash
from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy_continuum import make_versioned
from sqlalchemy_continuum.plugins import FlaskPlugin, ActivityPlugin
import re import re
from hashids import Hashids from hashids import Hashids
activity_plugin = ActivityPlugin()
make_versioned(plugins=[FlaskPlugin(), activity_plugin])
doc_hashids = Hashids(min_length=8) doc_hashids = Hashids(min_length=8)
Base = declarative_base() Base = declarative_base()
@ -119,7 +123,9 @@ class User(TimeStampedModel, UserMixin):
return user return user
class Item(TimeStampedModel): class Item(TimeStampedModel):
__versioned__ = {}
__tablename__ = 'item' __tablename__ = 'item'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('user.id')) user_id = Column(Integer, ForeignKey('user.id'))
published = Column(DateTime) published = Column(DateTime)
@ -148,7 +154,19 @@ class Item(TimeStampedModel):
@property @property
def url(self): def url(self):
return url_for('.view_item', username=self.user.username, hashid=self.hashid) return url_for('.view_item',
username=self.user.username,
hashid=self.hashid)
def version_url(self, version):
return url_for('.view_item',
username=self.user.username,
hashid=self.hashid,
v=version.transaction_id)
@property
def history_url(self):
return url_for('.history', username=self.user.username, hashid=self.hashid)
@property @property
def external_url(self): def external_url(self):
@ -245,3 +263,6 @@ class SourceDoc(Item):
return self.db_price_per_character or self.db_document_price / len(self.text) return self.db_price_per_character or self.db_document_price / len(self.text)
__mapper_args__ = {'polymorphic_identity': 'sourcedoc'} __mapper_args__ = {'polymorphic_identity': 'sourcedoc'}
configure_mappers()

View file

@ -21,7 +21,7 @@
</li> </li>
{% endif %} {% endif %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="#">History</a> <a class="nav-link" href="{{ doc.history_url }}">History</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" href="{{ doc.edit_url }}">Edit</a> <a class="nav-link active" href="{{ doc.edit_url }}">Edit</a>

View file

@ -0,0 +1,35 @@
{% extends "base.html" %}
{% block title %}{{ doc.title() }}{% endblock %}
{% block content %}
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link" href="{{ doc.url }}">View</a>
</li>
{% if doc.type == 'xanadoc' %}
<li class="nav-item">
<a class="nav-link" href="{{ doc.url }}/fulfil">Fulfil</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link active" href="{{ doc.history_url }}">History</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ doc.edit_url }}">Edit</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ request.url }}/raw">Raw</a>
</li>
</ul>
<h1 class="mt-3">{{ self.title() }}</h1>
<ul>
{% for v in doc.versions %}
<li><a href="{{ doc.version_url(v) }}">{{ v.modified.strftime('%H:%M:%S, %d %B %Y') }}</a></li>
{% endfor %}
</ul>
{% endblock %}

View file

@ -14,7 +14,7 @@
</li> </li>
{% endif %} {% endif %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="#">History</a> <a class="nav-link" href="{{ doc.history_url }}">History</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ doc.edit_url }}">Edit</a> <a class="nav-link" href="{{ doc.edit_url }}">Edit</a>
@ -25,11 +25,15 @@
</ul> </ul>
<h1 class="mt-3">{{ self.title() }}</h1> <h1 class="mt-3">{{ self.title() }}</h1>
{% if version %}
<p>Revision as of {{ version.modified.strftime('%H:%M, %d %B %Y') }}</p>
{% endif %}
<div id="text"> <div class="card mb-2" id="text">
<div class="card-block">
{% if span_length %} {% if span_length %}
{%- for start, line in add_highlight(doc.text, span_start, span_length) if line -%} {%- for start, line in add_highlight(text, span_start, span_length) if line -%}
<p data-start="{{ start }}"> <p class="card-text" data-start="{{ start }}">
{% for i in line %} {% for i in line %}
{%- if i.highlight -%} {%- if i.highlight -%}
{%- if i.highlight != '\n' and i.highlight != '\r\n' -%} {%- if i.highlight != '\n' and i.highlight != '\r\n' -%}
@ -42,15 +46,16 @@
</p> </p>
{%- endfor -%} {%- endfor -%}
{% else %} {% else %}
{%- for start, line in iter_lines(doc.text) if line -%} {%- for start, line in iter_lines(text) if line -%}
{# <p data-start="{{ start }}">{% if line != "\n" and line != "\r\n" %}{{ line }}{% else %}&nbsp;{% endif {# <p data-start="{{ start }}">{% if line != "\n" and line != "\r\n" %}{{ line }}{% else %}&nbsp;{% endif
%}</p>#} %}</p>#}
<p data-start="{{ start }}">{{ nbsp_at_start(line) }}</p> <p data-start="{{ start }}">{{ nbsp_at_start(line) }}</p>
{%- endfor -%} {%- endfor -%}
{% endif %} {% endif %}
</div> </div>
</div>
{% if doc.type == 'sourcedoc' and not span_length %} {% if not version and doc.type == 'sourcedoc' and not span_length %}
<button id="show-span-selector" class="btn btn-primary">show span selector</button> <button id="show-span-selector" class="btn btn-primary">show span selector</button>
<button id="select-all" class="btn btn-primary">get entire document span</button> <button id="select-all" class="btn btn-primary">get entire document span</button>
<p id="span-selector" class="d-none">span: <span id="span"></span></p> <p id="span-selector" class="d-none">span: <span id="span"></span></p>
@ -60,7 +65,7 @@
{% block scripts %} {% block scripts %}
<script> <script>
var doc_url = '{{ doc.external_url }}'; var doc_url = '{{ doc.external_url }}';
var doc_length = {{ doc.text | length }}; var doc_length = {{ text | length }};
</script> </script>
<script src="{{ url_for('static', filename='js/sourcedoc.js') }}"></script> <script src="{{ url_for('static', filename='js/sourcedoc.js') }}"></script>
{% endblock %} {% endblock %}

View file

@ -16,6 +16,7 @@ from jinja2 import evalcontextfilter, Markup
from functools import wraps from functools import wraps
from .utils import nbsp_at_start from .utils import nbsp_at_start
from itsdangerous import URLSafeTimedSerializer from itsdangerous import URLSafeTimedSerializer
from sqlalchemy_continuum import version_class
import re import re
@ -212,14 +213,31 @@ def view_item(username, hashid, raw=False):
if raw: if raw:
return Response(item.text, mimetype='text/plain') return Response(item.text, mimetype='text/plain')
if 'v' in request.args and request.args['v'].isdigit():
ItemVersion = version_class(Item)
version = (session.query(ItemVersion)
.filter_by(transaction_id=int(request.args['v']))
.first())
text = version.text
else:
version = None
text = item.text
return render_template('view.html', return render_template('view.html',
doc=item, doc=item,
version=version,
text=text,
span_start=start, span_start=start,
span_length=length, span_length=length,
add_highlight=add_highlight, add_highlight=add_highlight,
nbsp_at_start=nbsp_at_start, nbsp_at_start=nbsp_at_start,
iter_lines=iter_lines) iter_lines=iter_lines)
@bp.route('/<username>/<hashid>/history')
def history(username, hashid):
item = get_item(username, hashid)
return render_template('history.html', doc=item)
@bp.route('/<username>/<hashid>/edit', methods=['GET', 'POST']) @bp.route('/<username>/<hashid>/edit', methods=['GET', 'POST'])
def edit_item(username, hashid): def edit_item(username, hashid):
obj = get_item(username, hashid) obj = get_item(username, hashid)