| |
@@ -1,686 +0,0 @@
|
| |
- # -*- coding: utf-8 -*-
|
| |
- #
|
| |
- # Copyright © 2015 Red Hat, Inc.
|
| |
- #
|
| |
- # This copyrighted material is made available to anyone wishing to use,
|
| |
- # modify, copy, or redistribute it subject to the terms and conditions
|
| |
- # of the GNU Lesser General Public License (LGPL) version 2, or
|
| |
- # (at your option) any later version. This program is distributed in the
|
| |
- # hope that it will be useful, but WITHOUT ANY WARRANTY expressed or
|
| |
- # implied, including the implied warranties of MERCHANTABILITY or FITNESS
|
| |
- # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
| |
- # more details. You should have received a copy of the GNU Lesser General
|
| |
- # Public License along with this program; if not, write to the Free
|
| |
- # Software Foundation, Inc.,
|
| |
- # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
| |
- #
|
| |
- # Any Red Hat trademarks that are incorporated in the source
|
| |
- # code or documentation are not subject to the GNU General Public
|
| |
- # License and may only be used or replicated with the express permission
|
| |
- # of Red Hat, Inc.
|
| |
- #
|
| |
-
|
| |
- from __future__ import unicode_literals
|
| |
-
|
| |
- import datetime
|
| |
- import json
|
| |
- import logging
|
| |
- import operator
|
| |
- import os
|
| |
- import random
|
| |
- from collections import defaultdict
|
| |
-
|
| |
- import bleach
|
| |
- import flask
|
| |
- import sqlalchemy as sa
|
| |
- from sqlalchemy.orm import relation
|
| |
- from sqlalchemy.orm import backref
|
| |
- from sqlalchemy.orm.session import object_session
|
| |
-
|
| |
- import hubs.defaults
|
| |
- import hubs.widgets
|
| |
- from hubs.authz import ObjectAuthzMixin, AccessLevel
|
| |
- from hubs.database import BASE, Session
|
| |
- from hubs.utils import username2avatar
|
| |
- from hubs.signals import hub_created, user_created
|
| |
-
|
| |
- log = logging.getLogger(__name__)
|
| |
-
|
| |
-
|
| |
- def randomheader():
|
| |
- location = '/static/img/headers/'
|
| |
- header_dir = os.path.dirname(__file__) + location
|
| |
- choice = random.choice(os.listdir(header_dir))
|
| |
- return location + choice
|
| |
-
|
| |
-
|
| |
- ROLES = ['subscriber', 'member', 'owner', 'stargazer']
|
| |
-
|
| |
-
|
| |
- class Association(BASE):
|
| |
- __tablename__ = 'association'
|
| |
-
|
| |
- hub_id = sa.Column(sa.String(50),
|
| |
- sa.ForeignKey('hubs.name'),
|
| |
- primary_key=True)
|
| |
- user_id = sa.Column(sa.Text,
|
| |
- sa.ForeignKey('users.username'),
|
| |
- primary_key=True)
|
| |
- role = sa.Column(
|
| |
- sa.Enum(*ROLES, name="roles"), primary_key=True)
|
| |
-
|
| |
- user = relation("User", backref=backref(
|
| |
- 'associations', cascade="all, delete, delete-orphan"))
|
| |
- hub = relation("Hub", backref=backref(
|
| |
- 'associations', cascade="all, delete, delete-orphan"))
|
| |
-
|
| |
- @classmethod
|
| |
- def get(cls, hub, user, role):
|
| |
- return cls.query\
|
| |
- .filter_by(hub=hub)\
|
| |
- .filter_by(user=user)\
|
| |
- .filter_by(role=role)\
|
| |
- .first()
|
| |
-
|
| |
-
|
| |
- class Hub(ObjectAuthzMixin, BASE):
|
| |
- __tablename__ = 'hubs'
|
| |
- name = sa.Column(sa.String(50), primary_key=True)
|
| |
- created_on = sa.Column(sa.DateTime, default=datetime.datetime.utcnow)
|
| |
- widgets = relation('Widget', cascade='all,delete', backref='hub',
|
| |
- order_by="Widget.index")
|
| |
- config = relation('HubConfig', uselist=False, cascade='all,delete',
|
| |
- backref='hub')
|
| |
- archived = sa.Column(sa.Boolean, default=False)
|
| |
- user_hub = sa.Column(sa.Boolean, default=False)
|
| |
- # Timestamps about various kinds of "freshness"
|
| |
- last_refreshed = sa.Column(sa.DateTime, default=datetime.datetime.utcnow)
|
| |
- last_edited = sa.Column(sa.DateTime, default=datetime.datetime.utcnow)
|
| |
-
|
| |
- # fas_group = sa.Column(sa.String(32), nullable=False)
|
| |
-
|
| |
- @property
|
| |
- def days_idle(self):
|
| |
- return (datetime.datetime.utcnow() - self.last_refreshed).days
|
| |
-
|
| |
- @property
|
| |
- def activity_class(self):
|
| |
- idle = self.days_idle
|
| |
- limits = [
|
| |
- (356 * 5, '5years'),
|
| |
- (356 * 2, '2years'),
|
| |
- (356, 'year'),
|
| |
- (31 * 3, 'quarter'),
|
| |
- (31, 'month'),
|
| |
- (7, 'week'),
|
| |
- (1, 'day'),
|
| |
- (0, 'none'),
|
| |
- ]
|
| |
- for limit, name in limits:
|
| |
- if idle > limit:
|
| |
- return name
|
| |
-
|
| |
- @property
|
| |
- def owners(self):
|
| |
- return [assoc.user for assoc in self.associations
|
| |
- if assoc.role == 'owner']
|
| |
-
|
| |
- @property
|
| |
- def members(self):
|
| |
- return [assoc.user for assoc in self.associations
|
| |
- if assoc.role == 'member' or assoc.role == 'owner']
|
| |
-
|
| |
- @property
|
| |
- def subscribers(self):
|
| |
- return [assoc.user for assoc in self.associations
|
| |
- if assoc.role == 'subscriber']
|
| |
-
|
| |
- @property
|
| |
- def stargazers(self):
|
| |
- return [assoc.user for assoc in self.associations
|
| |
- if assoc.role == 'stargazer']
|
| |
-
|
| |
- def subscribe(self, user, role='subscriber'):
|
| |
- """ Subscribe a user to this hub. """
|
| |
- # TODO -- add logic here to manage not adding the user multiple
|
| |
- # times, doing different roles, etc.. publish a fedmsg message,
|
| |
- # etc...
|
| |
- session = object_session(self)
|
| |
- session.add(Association(user=user, hub=self, role=role))
|
| |
- session.commit()
|
| |
-
|
| |
- def unsubscribe(self, user, role='subscriber'):
|
| |
- """ Unsubscribe a user to this hub. """
|
| |
- # TODO -- add logic here to manage not adding the user multiple
|
| |
- # times, doing different roles, etc.. publish a fedmsg message,
|
| |
- # etc...
|
| |
- session = object_session(self)
|
| |
- association = Association.get(hub=self, user=user, role=role)
|
| |
- if not association:
|
| |
- raise KeyError("%r is not a %r of %r" % (user, role, self))
|
| |
- if role == 'owner':
|
| |
- # When stepping down from an owner, turn into a member.
|
| |
- is_member = bool(Association.query.filter_by(
|
| |
- hub=self, user=user, role="member").count())
|
| |
- if is_member:
|
| |
- session.delete(association)
|
| |
- else:
|
| |
- association.role = 'member'
|
| |
- else:
|
| |
- session.delete(association)
|
| |
- session.commit()
|
| |
-
|
| |
- @classmethod
|
| |
- def by_name(cls, name):
|
| |
- return cls.query.filter_by(name=name).first()
|
| |
-
|
| |
- get = by_name
|
| |
-
|
| |
- @classmethod
|
| |
- def all_group_hubs(cls):
|
| |
- return cls.query.filter_by(user_hub=False).all()
|
| |
-
|
| |
- @classmethod
|
| |
- def all_user_hubs(cls):
|
| |
- return cls.query.filter_by(user_hub=True).all()
|
| |
-
|
| |
- @classmethod
|
| |
- def create_user_hub(cls, username, fullname):
|
| |
- session = Session()
|
| |
- hub = cls(name=username, user_hub=True)
|
| |
- session.add(hub)
|
| |
- hub_config = HubConfig(
|
| |
- hub=hub, summary=fullname, avatar=username2avatar(username))
|
| |
- session.add(hub_config)
|
| |
- session.flush()
|
| |
- hub_created.send(hub)
|
| |
- return hub
|
| |
-
|
| |
- @classmethod
|
| |
- def create_group_hub(cls, name, summary, **extra):
|
| |
- session = Session()
|
| |
- hub = cls(name=name, user_hub=False)
|
| |
- session.add(hub)
|
| |
- # TODO -- do something else, smarter for group avatars
|
| |
- hub_config = HubConfig(
|
| |
- hub=hub, summary=summary, avatar=username2avatar(name))
|
| |
- session.add(hub_config)
|
| |
- session.flush()
|
| |
- hub_created.send(hub, **extra)
|
| |
- return hub
|
| |
-
|
| |
- def on_created(self, **extra):
|
| |
- if self.user_hub:
|
| |
- hubs.defaults.add_user_widgets(self)
|
| |
- user = User.query.get(self.name)
|
| |
- self.subscribe(user, role='owner')
|
| |
- else:
|
| |
- hubs.defaults.add_group_widgets(self, **extra)
|
| |
-
|
| |
- def on_updated(self, old_config):
|
| |
- for widget_instance in self.widgets:
|
| |
- if not widget_instance.enabled:
|
| |
- continue
|
| |
- widget = widget_instance.module
|
| |
- new_config = self.config.__json__()
|
| |
- will_reload = False
|
| |
- cached_functions = widget.get_cached_functions()
|
| |
- for fn_name, fn_class in cached_functions.items():
|
| |
- fn = fn_class(widget_instance)
|
| |
- if fn.should_invalidate_on_hub_config_change(old_config):
|
| |
- flask.g.task_queue.enqueue(
|
| |
- "widget-cache",
|
| |
- idx=widget_instance.idx,
|
| |
- hub=self.name,
|
| |
- fn_name=fn_name,
|
| |
- )
|
| |
- will_reload = True
|
| |
- if not will_reload:
|
| |
- # Reload the widget if it has asked for it.
|
| |
- if widget.should_reload_on_hub_config_change(
|
| |
- old_config, new_config):
|
| |
- flask.g.task_queue.enqueue(
|
| |
- "widget-update",
|
| |
- idx=widget_instance.idx,
|
| |
- hub=self.name,
|
| |
- )
|
| |
-
|
| |
- def _get_auth_user_access_level(self, user):
|
| |
- # overridden to handle user hubs.
|
| |
- if self.user_hub and user.username == self.name:
|
| |
- return AccessLevel.owner
|
| |
- return super(Hub, self)._get_auth_user_access_level(user)
|
| |
-
|
| |
- def _get_auth_group(self):
|
| |
- # While we're local-only, just use the hub name:
|
| |
- return self.name
|
| |
- # When the CAIAPI is in place we will be able to use the auth_group
|
| |
- # setting:
|
| |
- # group = self.config.auth_group
|
| |
- # if group is None:
|
| |
- # group = self.name
|
| |
- # return group
|
| |
-
|
| |
- def _get_auth_user_roles(self, user):
|
| |
- """Override until we can get groups from FAS.
|
| |
-
|
| |
- This will not return all roles, only the one for the current hub, but
|
| |
- that fine since it's the only one we're interested in when this method
|
| |
- is called.
|
| |
- """
|
| |
- roles = [
|
| |
- assoc.role for assoc in self.associations
|
| |
- if assoc.user.username == user.username
|
| |
- ]
|
| |
- return {self.name: roles}
|
| |
-
|
| |
- def _get_auth_permission_name(self, action):
|
| |
- if action == "view":
|
| |
- action = "{}.view".format(self.config.visibility)
|
| |
- return "hub.{}".format(action)
|
| |
-
|
| |
- def get_props(self):
|
| |
- """Get the hub properties for the Javascript UI"""
|
| |
- result = {
|
| |
- "name": self.name,
|
| |
- "config": self.config.__json__(),
|
| |
- "users": {role: [] for role in ROLES},
|
| |
- "mtime": self.last_refreshed,
|
| |
- "user_hub": self.user_hub,
|
| |
- }
|
| |
- for assoc in sorted(self.associations, key=lambda a: a.user.username):
|
| |
- if assoc.role not in ROLES:
|
| |
- continue
|
| |
- result["users"][assoc.role].append(assoc.user.__json__())
|
| |
- if self.user_hub:
|
| |
- user = User.query.get(self.name)
|
| |
- if user is None:
|
| |
- result["subscribed_to"] = []
|
| |
- else:
|
| |
- result["subscribed_to"] = [
|
| |
- assoc.hub.name for assoc in Association.query.filter_by(
|
| |
- user=user, role="subscriber")
|
| |
- ]
|
| |
- return result
|
| |
-
|
| |
- def __json__(self):
|
| |
- return {
|
| |
- 'name': self.name,
|
| |
- 'archived': self.archived,
|
| |
- 'config': self.config.__json__(),
|
| |
-
|
| |
- 'widgets': [widget.idx for widget in self.widgets],
|
| |
-
|
| |
- 'owners': [u.username for u in self.owners],
|
| |
- 'members': [u.username for u in self.members],
|
| |
- 'subscribers': [u.username for u in self.subscribers],
|
| |
- }
|
| |
-
|
| |
-
|
| |
- class HubConfig(BASE):
|
| |
-
|
| |
- __tablename__ = 'hubs_config'
|
| |
-
|
| |
- VISIBILITY = ["public", "preview", "private"]
|
| |
-
|
| |
- id = sa.Column(sa.Integer, primary_key=True)
|
| |
- hub_id = sa.Column(sa.String(50), sa.ForeignKey('hubs.name'),
|
| |
- nullable=False)
|
| |
- summary = sa.Column(sa.String(128))
|
| |
- left_width = sa.Column(sa.Integer, nullable=False, default=8)
|
| |
- # A URL to the "avatar" for this hub.
|
| |
- avatar = sa.Column(sa.String(256), default="")
|
| |
- header_img = sa.Column(sa.String(256), default=randomheader)
|
| |
- chat_channel = sa.Column(sa.String(256), nullable=True)
|
| |
- chat_domain = sa.Column(sa.String(256), nullable=True)
|
| |
- auth_group = sa.Column(sa.String(256), nullable=True)
|
| |
- visibility = sa.Column(
|
| |
- sa.Enum(*VISIBILITY, name="hub_visibility"),
|
| |
- default="public", nullable=False)
|
| |
-
|
| |
- def __json__(self):
|
| |
- return {
|
| |
- 'summary': self.summary,
|
| |
- 'left_width': self.left_width,
|
| |
- 'right_width': self.right_width,
|
| |
- 'avatar': self.avatar,
|
| |
- 'chat_channel': self.chat_channel,
|
| |
- 'chat_domain': self.chat_domain,
|
| |
- 'visibility': self.visibility,
|
| |
- }
|
| |
-
|
| |
- @property
|
| |
- def right_width(self):
|
| |
- return 12 - self.left_width
|
| |
-
|
| |
- @right_width.setter
|
| |
- def right_width(self, value):
|
| |
- self.left_width = 12 - value
|
| |
-
|
| |
-
|
| |
- class SpecificDefaultDict(defaultdict):
|
| |
- """A more specific version of defaultdict.
|
| |
-
|
| |
- This class behaves like defaultdict, but calls the ``default_factory`` with
|
| |
- the key as first argument.
|
| |
- """
|
| |
-
|
| |
- def __missing__(self, key):
|
| |
- if self.default_factory is None:
|
| |
- return super(SpecificDefaultDict, self).__missing__(key)
|
| |
- self[key] = self.default_factory(key)
|
| |
- return self[key]
|
| |
-
|
| |
-
|
| |
- class Widget(ObjectAuthzMixin, BASE):
|
| |
-
|
| |
- __tablename__ = 'widgets'
|
| |
-
|
| |
- VISIBILITY = ["public", "restricted"]
|
| |
-
|
| |
- idx = sa.Column(sa.Integer, primary_key=True)
|
| |
- plugin = sa.Column(sa.String(50), nullable=False)
|
| |
- created_on = sa.Column(sa.DateTime, default=datetime.datetime.utcnow)
|
| |
- hub_id = sa.Column(sa.String(50), sa.ForeignKey('hubs.name'))
|
| |
- _config = sa.Column(sa.Text, default="{}")
|
| |
-
|
| |
- index = sa.Column(sa.Integer, nullable=False)
|
| |
- left = sa.Column(sa.Boolean, nullable=False, default=False)
|
| |
- visibility = sa.Column(
|
| |
- sa.Enum(*VISIBILITY, name="widget_visibility"),
|
| |
- default="public", nullable=False)
|
| |
-
|
| |
- @classmethod
|
| |
- def by_idx(cls, idx):
|
| |
- return cls.query.filter_by(idx=idx).first()
|
| |
-
|
| |
- @classmethod
|
| |
- def by_plugin(cls, plugin):
|
| |
- return cls.query.filter_by(plugin=plugin).first()
|
| |
-
|
| |
- @classmethod
|
| |
- def by_hub_id_all(cls, hub_id):
|
| |
- return cls.query.filter_by(hub_id=hub_id).all()
|
| |
-
|
| |
- get = by_idx
|
| |
-
|
| |
- @property
|
| |
- def config(self):
|
| |
- def get_default(key):
|
| |
- for param in self.module.get_parameters():
|
| |
- if key == param.name:
|
| |
- break
|
| |
- else:
|
| |
- raise KeyError("No such parameter")
|
| |
- return param.default
|
| |
-
|
| |
- value = SpecificDefaultDict(get_default)
|
| |
- value.update(json.loads(self._config))
|
| |
- return value
|
| |
-
|
| |
- @config.setter
|
| |
- def config(self, config):
|
| |
- self._config = json.dumps(config)
|
| |
-
|
| |
- def on_updated(self, old_config):
|
| |
- will_reload = False
|
| |
- cached_functions = self.module.get_cached_functions()
|
| |
- for fn_name, fn_class in cached_functions.items():
|
| |
- fn = fn_class(self)
|
| |
- if fn.should_invalidate_on_widget_config_change(old_config):
|
| |
- flask.g.task_queue.enqueue(
|
| |
- "widget-cache",
|
| |
- idx=self.idx,
|
| |
- hub=self.hub.name,
|
| |
- fn_name=fn_name,
|
| |
- )
|
| |
- will_reload = True
|
| |
- if not will_reload:
|
| |
- # Reload the widget nonetheless because the config
|
| |
- # change may impact rendering.
|
| |
- flask.g.task_queue.enqueue(
|
| |
- "widget-update",
|
| |
- idx=self.idx,
|
| |
- hub=self.hub.name,
|
| |
- )
|
| |
-
|
| |
- def _get_auth_access_level(self, user):
|
| |
- return self.hub._get_auth_access_level(user)
|
| |
-
|
| |
- def _get_auth_user_roles(self, user):
|
| |
- """Override until we can get groups from FAS"""
|
| |
- return self.hub._get_auth_user_roles(user)
|
| |
-
|
| |
- def _get_auth_permission_name(self, action):
|
| |
- if action != "view":
|
| |
- return self.hub._get_auth_permission_name(action)
|
| |
- hub_visibility = self.hub.config.visibility
|
| |
- if hub_visibility != "preview":
|
| |
- return "hub.{}.view".format(hub_visibility)
|
| |
- return "widget.{}.view".format(self.visibility)
|
| |
-
|
| |
- def __json__(self):
|
| |
- module = hubs.widgets.registry[self.plugin]
|
| |
- root_view = module.get_views()["root"](module)
|
| |
- data = root_view.get_context(self)
|
| |
- data.update(root_view.get_extra_context(self))
|
| |
- data.pop('widget', None)
|
| |
- data.pop('widget_instance', None)
|
| |
- return {
|
| |
- 'id': self.idx,
|
| |
- # TODO -- use flask.url_for to get the url for this widget
|
| |
- 'plugin': self.plugin,
|
| |
- 'description': module.__doc__,
|
| |
- 'hub': self.hub_id,
|
| |
- 'left': self.left,
|
| |
- 'index': self.index,
|
| |
- 'data': data,
|
| |
- 'config': self.config,
|
| |
- }
|
| |
-
|
| |
- def __repr__(self):
|
| |
- return "<Widget %s /%s/%i>" % (self.plugin, self.hub.name, self.idx)
|
| |
-
|
| |
- @property
|
| |
- def module(self):
|
| |
- return hubs.widgets.registry[self.plugin]
|
| |
-
|
| |
- def get_props(self, with_secret_config=False):
|
| |
- return self.module.get_props(self, with_secret_config)
|
| |
-
|
| |
- @property
|
| |
- def enabled(self):
|
| |
- return self.plugin in hubs.widgets.registry
|
| |
-
|
| |
-
|
| |
- class User(BASE):
|
| |
- __tablename__ = 'users'
|
| |
- username = sa.Column(sa.Text, primary_key=True)
|
| |
- fullname = sa.Column(sa.Text)
|
| |
- created_on = sa.Column(sa.DateTime, default=datetime.datetime.utcnow)
|
| |
- saved_notifications = relation('SavedNotification', backref='users',
|
| |
- lazy='dynamic')
|
| |
-
|
| |
- def __json__(self):
|
| |
- return {
|
| |
- 'username': self.username,
|
| |
- 'avatar': username2avatar(self.username),
|
| |
- 'fullname': self.fullname,
|
| |
- 'created_on': self.created_on,
|
| |
- # We'll need hubs subscribed to, owned, etc..
|
| |
- # 'hubs': [hub.idx for hub in self.hubx],
|
| |
- }
|
| |
-
|
| |
- @property
|
| |
- def ownerships(self):
|
| |
- return [assoc.hub for assoc in self.associations
|
| |
- if assoc.role == 'owner']
|
| |
-
|
| |
- @property
|
| |
- def memberships(self):
|
| |
- return [assoc.hub for assoc in self.associations
|
| |
- if assoc.role == 'member' or assoc.role == 'owner']
|
| |
-
|
| |
- @property
|
| |
- def subscriptions(self):
|
| |
- return [assoc.hub for assoc in self.associations
|
| |
- if assoc.role == 'subscriber']
|
| |
-
|
| |
- @property
|
| |
- def starred_hubs(self):
|
| |
- return [assoc.hub for assoc in self.associations
|
| |
- if assoc.role == 'stargazer']
|
| |
-
|
| |
- @property
|
| |
- def bookmarks(self):
|
| |
- bookmarks = {
|
| |
- "starred": [],
|
| |
- "memberships": [],
|
| |
- "subscriptions": [],
|
| |
- }
|
| |
- starred_hubs = self.starred_hubs
|
| |
- memberships = self.memberships
|
| |
- for assoc in self.associations:
|
| |
- if assoc.hub.name == self.username:
|
| |
- continue
|
| |
-
|
| |
- if assoc.role == "stargazer":
|
| |
- bookmarks["starred"].append(assoc.hub)
|
| |
-
|
| |
- if ((assoc.role == "member" or assoc.role == "owner")
|
| |
- and assoc.hub not in starred_hubs):
|
| |
- bookmarks["memberships"].append(assoc.hub)
|
| |
-
|
| |
- if (assoc.role == "subscriber"
|
| |
- and assoc.hub not in starred_hubs
|
| |
- and assoc.hub not in memberships):
|
| |
- bookmarks["subscriptions"].append(assoc.hub)
|
| |
-
|
| |
- bookmarks = dict(
|
| |
- (key, sorted(list(set(values)), key=operator.attrgetter('name')))
|
| |
- for key, values in bookmarks.items()
|
| |
- )
|
| |
- return bookmarks
|
| |
-
|
| |
- @classmethod
|
| |
- def by_username(cls, username):
|
| |
- return cls.query.filter_by(username=username).first()
|
| |
-
|
| |
- get = by_username
|
| |
-
|
| |
- @classmethod
|
| |
- def all(cls):
|
| |
- return cls.query.all()
|
| |
-
|
| |
- @classmethod
|
| |
- def get_or_create(cls, username, fullname):
|
| |
- if not username:
|
| |
- raise ValueError("Must provide an username, not %r" % username)
|
| |
- self = cls.query.get(username)
|
| |
- if self is None:
|
| |
- self = cls.create(username, fullname)
|
| |
- return self
|
| |
-
|
| |
- @classmethod
|
| |
- def create(cls, username, fullname):
|
| |
- session = Session()
|
| |
- self = cls(username=username, fullname=fullname)
|
| |
- session.add(self)
|
| |
- session.flush()
|
| |
- user_created.send(self)
|
| |
- return self
|
| |
-
|
| |
- def on_created(self):
|
| |
- if Hub.query.get(self.username) is None:
|
| |
- Hub.create_user_hub(self.username, self.fullname)
|
| |
-
|
| |
-
|
| |
- class VisitCounter(BASE):
|
| |
- __tablename__ = 'visit_counter'
|
| |
- count = sa.Column(sa.Integer, default=0, nullable=False)
|
| |
-
|
| |
- visited_hub = sa.Column(sa.String(50), sa.ForeignKey('hubs.name'),
|
| |
- primary_key=True)
|
| |
-
|
| |
- username = sa.Column(sa.Text, sa.ForeignKey('users.username'),
|
| |
- primary_key=True)
|
| |
-
|
| |
- user = relation("User", backref=backref(
|
| |
- 'visit_counters', cascade="all, delete, delete-orphan"))
|
| |
- hub = relation("Hub", backref=backref(
|
| |
- 'visit_counters', cascade="all, delete, delete-orphan"))
|
| |
-
|
| |
- @classmethod
|
| |
- def by_username(cls, username):
|
| |
- return cls.query.filter_by(username=username).all()
|
| |
-
|
| |
- @classmethod
|
| |
- def get_visits_by_username_hub(cls, username, visited_hub):
|
| |
- return cls.query.filter_by(
|
| |
- username=username, visited_hub=visited_hub).first()
|
| |
-
|
| |
- @classmethod
|
| |
- def increment_visits(cls, username, visited_hub):
|
| |
- row = cls.get_or_create(username=username,
|
| |
- visited_hub=visited_hub)
|
| |
- row.count += 1
|
| |
-
|
| |
- @classmethod
|
| |
- def get_or_create(cls, username, visited_hub):
|
| |
- if not username:
|
| |
- raise ValueError("Must provide an username, not %r" % username)
|
| |
- if not visited_hub:
|
| |
- raise ValueError("Must provide an hub, not %r" % visited_hub)
|
| |
- hub_exists = Hub.query.get(visited_hub) is not None
|
| |
- user_exists = User.query.get(username) is not None
|
| |
- if not hub_exists or not user_exists:
|
| |
- raise ValueError("Must provide a hub/user that exists")
|
| |
-
|
| |
- self = cls.query.filter_by(
|
| |
- username=username, visited_hub=visited_hub).first()
|
| |
- if self is None:
|
| |
- session = Session()
|
| |
- self = cls(username=username, visited_hub=visited_hub)
|
| |
- session.add(self)
|
| |
- session.flush()
|
| |
- return self
|
| |
-
|
| |
-
|
| |
- class SavedNotification(BASE):
|
| |
- __tablename__ = 'savednotifications'
|
| |
- user = sa.Column(sa.Text, sa.ForeignKey('users.username'))
|
| |
-
|
| |
- created = sa.Column(sa.DateTime, default=datetime.datetime.utcnow)
|
| |
- dom_id = sa.Column(sa.Text)
|
| |
- idx = sa.Column(sa.Integer, primary_key=True)
|
| |
- link = sa.Column(sa.Text)
|
| |
- markup = sa.Column(sa.Text)
|
| |
- secondary_icon = sa.Column(sa.Text)
|
| |
-
|
| |
- def __init__(self, username=None, markup='', link='', secondary_icon='',
|
| |
- dom_id=''):
|
| |
- self.user = username
|
| |
- self.markup = markup
|
| |
- self.link = link
|
| |
- self.secondary_icon = secondary_icon
|
| |
- self.dom_id = dom_id
|
| |
-
|
| |
- def __json__(self):
|
| |
- return {
|
| |
- 'created': str(self.created),
|
| |
- 'date_time': str(self.created),
|
| |
- 'dom_id': self.dom_id,
|
| |
- 'idx': self.idx,
|
| |
- 'link': bleach.linkify(self.link),
|
| |
- 'markup': bleach.linkify(self.markup),
|
| |
- 'saved': True,
|
| |
- 'secondary_icon': self.secondary_icon
|
| |
- }
|
| |
-
|
| |
- @classmethod
|
| |
- def by_username(cls, username):
|
| |
- return cls.query.filter_by(user=username).all()
|
| |
-
|
| |
- @classmethod
|
| |
- def all(cls):
|
| |
- return cls.query.all()
|
| |
This is a big PR that reorganizes the way the hub configuration is stored in the database.
Feel free to read the commits in order, they should be roughly topical.
There is no support for migrating the configuration, so you'll have to remove your DB and run
populate.py
again.When this PR is accepted, I'll split the
models.py
file in multiple sub-modules, because it's getting complicated and I think it would be clearer with a table per file, but I did not want to generate noise for this review.