aboutsummaryrefslogtreecommitdiffstats
path: root/mediagoblin/db/models.py
diff options
context:
space:
mode:
Diffstat (limited to 'mediagoblin/db/models.py')
-rw-r--r--mediagoblin/db/models.py500
1 files changed, 421 insertions, 79 deletions
diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py
index 2ff30d22..e8fb17a7 100644
--- a/mediagoblin/db/models.py
+++ b/mediagoblin/db/models.py
@@ -18,13 +18,15 @@
TODO: indexes on foreignkeys, where useful.
"""
+from __future__ import print_function
+
import logging
import datetime
from sqlalchemy import Column, Integer, Unicode, UnicodeText, DateTime, \
Boolean, ForeignKey, UniqueConstraint, PrimaryKeyConstraint, \
SmallInteger, Date
-from sqlalchemy.orm import relationship, backref, with_polymorphic
+from sqlalchemy.orm import relationship, backref, with_polymorphic, validates
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.sql.expression import desc
from sqlalchemy.ext.associationproxy import association_proxy
@@ -34,20 +36,90 @@ from mediagoblin.db.extratypes import (PathTupleWithSlashes, JSONEncoded,
MutationDict)
from mediagoblin.db.base import Base, DictReadAttrProxy
from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, \
- MediaCommentMixin, CollectionMixin, CollectionItemMixin
+ MediaCommentMixin, CollectionMixin, CollectionItemMixin, \
+ ActivityMixin
from mediagoblin.tools.files import delete_media_files
from mediagoblin.tools.common import import_component
+from mediagoblin.tools.routing import extract_url_arguments
-# It's actually kind of annoying how sqlalchemy-migrate does this, if
-# I understand it right, but whatever. Anyway, don't remove this :P
-#
-# We could do migration calls more manually instead of relying on
-# this import-based meddling...
-from migrate import changeset
+import six
+from pytz import UTC
_log = logging.getLogger(__name__)
+class Location(Base):
+ """ Represents a physical location """
+ __tablename__ = "core__locations"
+
+ id = Column(Integer, primary_key=True)
+ name = Column(Unicode)
+
+ # GPS coordinates
+ position = Column(MutationDict.as_mutable(JSONEncoded))
+ address = Column(MutationDict.as_mutable(JSONEncoded))
+
+ @classmethod
+ def create(cls, data, obj):
+ location = cls()
+ location.unserialize(data)
+ location.save()
+ obj.location = location.id
+ return location
+
+ def serialize(self, request):
+ location = {"objectType": "place"}
+
+ if self.name is not None:
+ location["displayName"] = self.name
+
+ if self.position:
+ location["position"] = self.position
+ if self.address:
+ location["address"] = self.address
+
+ return location
+
+ def unserialize(self, data):
+ if "displayName" in data:
+ self.name = data["displayName"]
+
+ self.position = {}
+ self.address = {}
+
+ # nicer way to do this?
+ if "position" in data:
+ # TODO: deal with ISO 9709 formatted string as position
+ if "altitude" in data["position"]:
+ self.position["altitude"] = data["position"]["altitude"]
+
+ if "direction" in data["position"]:
+ self.position["direction"] = data["position"]["direction"]
+
+ if "longitude" in data["position"]:
+ self.position["longitude"] = data["position"]["longitude"]
+
+ if "latitude" in data["position"]:
+ self.position["latitude"] = data["position"]["latitude"]
+
+ if "address" in data:
+ if "formatted" in data["address"]:
+ self.address["formatted"] = data["address"]["formatted"]
+
+ if "streetAddress" in data["address"]:
+ self.address["streetAddress"] = data["address"]["streetAddress"]
+
+ if "locality" in data["address"]:
+ self.address["locality"] = data["address"]["locality"]
+
+ if "region" in data["address"]:
+ self.address["region"] = data["address"]["region"]
+
+ if "postalCode" in data["address"]:
+ self.address["postalCode"] = data["addresss"]["postalCode"]
+
+ if "country" in data["address"]:
+ self.address["country"] = data["address"]["country"]
class User(Base, UserMixin):
"""
@@ -64,7 +136,7 @@ class User(Base, UserMixin):
# point.
email = Column(Unicode, nullable=False)
pw_hash = Column(Unicode)
- created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
# Intented to be nullable=False, but migrations would not work for it
# set to nullable=True implicitly.
wants_comment_notification = Column(Boolean, default=True)
@@ -74,6 +146,10 @@ class User(Base, UserMixin):
bio = Column(UnicodeText) # ??
uploaded = Column(Integer, default=0)
upload_limit = Column(Integer)
+ location = Column(Integer, ForeignKey("core__locations.id"))
+ get_location = relationship("Location", lazy="joined")
+
+ activity = Column(Integer, ForeignKey("core__activity_intermediators.id"))
## TODO
# plugin data would be in a separate model
@@ -138,11 +214,13 @@ class User(Base, UserMixin):
def serialize(self, request):
+ published = UTC.localize(self.created)
user = {
"id": "acct:{0}@{1}".format(self.username, request.host),
+ "published": published.isoformat(),
"preferredUsername": self.username,
"displayName": "{0}@{1}".format(self.username, request.host),
- "objectType": "person",
+ "objectType": self.object_type,
"pump_io": {
"shared": False,
"followed": False,
@@ -150,21 +228,21 @@ class User(Base, UserMixin):
"links": {
"self": {
"href": request.urlgen(
- "mediagoblin.federation.user.profile",
+ "mediagoblin.api.user.profile",
username=self.username,
qualified=True
),
},
"activity-inbox": {
"href": request.urlgen(
- "mediagoblin.federation.inbox",
+ "mediagoblin.api.inbox",
username=self.username,
qualified=True
)
},
"activity-outbox": {
"href": request.urlgen(
- "mediagoblin.federation.feed",
+ "mediagoblin.api.feed",
username=self.username,
qualified=True
)
@@ -176,9 +254,18 @@ class User(Base, UserMixin):
user.update({"summary": self.bio})
if self.url:
user.update({"url": self.url})
+ if self.location:
+ user.update({"location": self.get_location.serialize(request)})
return user
+ def unserialize(self, data):
+ if "summary" in data:
+ self.bio = data["summary"]
+
+ if "location" in data:
+ Location.create(data, self)
+
class Client(Base):
"""
Model representing a client - Used for API Auth
@@ -189,8 +276,8 @@ class Client(Base):
secret = Column(Unicode, nullable=False)
expirey = Column(DateTime, nullable=True)
application_type = Column(Unicode, nullable=False)
- created = Column(DateTime, nullable=False, default=datetime.datetime.now)
- updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
+ updated = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
# optional stuff
redirect_uri = Column(JSONEncoded, nullable=True)
@@ -218,8 +305,10 @@ class RequestToken(Base):
authenticated = Column(Boolean, default=False)
verifier = Column(Unicode, nullable=True)
callback = Column(Unicode, nullable=False, default=u"oob")
- created = Column(DateTime, nullable=False, default=datetime.datetime.now)
- updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
+ updated = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
+
+ get_client = relationship(Client)
class AccessToken(Base):
"""
@@ -231,8 +320,10 @@ class AccessToken(Base):
secret = Column(Unicode, nullable=False)
user = Column(Integer, ForeignKey(User.id))
request_token = Column(Unicode, ForeignKey(RequestToken.token))
- created = Column(DateTime, nullable=False, default=datetime.datetime.now)
- updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
+ updated = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
+
+ get_requesttoken = relationship(RequestToken)
class NonceTimestamp(Base):
@@ -254,7 +345,7 @@ class MediaEntry(Base, MediaEntryMixin):
uploader = Column(Integer, ForeignKey(User.id), nullable=False, index=True)
title = Column(Unicode, nullable=False)
slug = Column(Unicode)
- created = Column(DateTime, nullable=False, default=datetime.datetime.now,
+ created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow,
index=True)
description = Column(UnicodeText) # ??
media_type = Column(Unicode, nullable=False)
@@ -262,6 +353,8 @@ class MediaEntry(Base, MediaEntryMixin):
# or use sqlalchemy.types.Enum?
license = Column(Unicode)
file_size = Column(Integer, default=0)
+ location = Column(Integer, ForeignKey("core__locations.id"))
+ get_location = relationship("Location", lazy="joined")
fail_error = Column(Unicode)
fail_metadata = Column(JSONEncoded)
@@ -309,6 +402,8 @@ class MediaEntry(Base, MediaEntryMixin):
media_metadata = Column(MutationDict.as_mutable(JSONEncoded),
default=MutationDict())
+ activity = Column(Integer, ForeignKey("core__activity_intermediators.id"))
+
## TODO
# fail_error
@@ -344,7 +439,7 @@ class MediaEntry(Base, MediaEntryMixin):
return the value of the key.
"""
media_file = MediaFile.query.filter_by(media_entry=self.id,
- name=unicode(file_key)).first()
+ name=six.text_type(file_key)).first()
if media_file:
if metadata_key:
@@ -357,11 +452,11 @@ class MediaEntry(Base, MediaEntryMixin):
Update the file_metadata of a MediaFile.
"""
media_file = MediaFile.query.filter_by(media_entry=self.id,
- name=unicode(file_key)).first()
+ name=six.text_type(file_key)).first()
file_metadata = media_file.file_metadata or {}
- for key, value in kwargs.iteritems():
+ for key, value in six.iteritems(kwargs):
file_metadata[key] = value
media_file.file_metadata = file_metadata
@@ -386,7 +481,7 @@ class MediaEntry(Base, MediaEntryMixin):
media_data.get_media_entry = self
else:
# Update old media data
- for field, value in kwargs.iteritems():
+ for field, value in six.iteritems(kwargs):
setattr(media_data, field, value)
@memoized_property
@@ -394,7 +489,11 @@ class MediaEntry(Base, MediaEntryMixin):
return import_component(self.media_type + '.models:BACKREF_NAME')
def __repr__(self):
- safe_title = self.title.encode('ascii', 'replace')
+ if six.PY2:
+ # obj.__repr__() should return a str on Python 2
+ safe_title = self.title.encode('utf-8', 'replace')
+ else:
+ safe_title = self.title
return '<{classname} {id}: {title}>'.format(
classname=self.__class__.__name__,
@@ -415,7 +514,7 @@ class MediaEntry(Base, MediaEntryMixin):
# Delete all related files/attachments
try:
delete_media_files(self)
- except OSError, error:
+ except OSError as error:
# Returns list of files we failed to delete
_log.error('No such files from the user "{1}" to delete: '
'{0}'.format(str(error), self.get_uploader))
@@ -430,38 +529,36 @@ class MediaEntry(Base, MediaEntryMixin):
# pass through commit=False/True in kwargs
super(MediaEntry, self).delete(**kwargs)
- @property
- def objectType(self):
- """ Converts media_type to pump-like type - don't use internally """
- return self.media_type.split(".")[-1]
-
def serialize(self, request, show_comments=True):
""" Unserialize MediaEntry to object """
+ href = request.urlgen(
+ "mediagoblin.api.object",
+ object_type=self.object_type,
+ id=self.id,
+ qualified=True
+ )
author = self.get_uploader
+ published = UTC.localize(self.created)
+ updated = UTC.localize(self.created)
context = {
- "id": self.id,
+ "id": href,
"author": author.serialize(request),
- "objectType": self.objectType,
- "url": self.url_for_self(request.urlgen),
+ "objectType": self.object_type,
+ "url": self.url_for_self(request.urlgen, qualified=True),
"image": {
"url": request.host_url + self.thumb_url[1:],
},
"fullImage":{
"url": request.host_url + self.original_url[1:],
},
- "published": self.created.isoformat(),
- "updated": self.created.isoformat(),
+ "published": published.isoformat(),
+ "updated": updated.isoformat(),
"pump_io": {
"shared": False,
},
"links": {
"self": {
- "href": request.urlgen(
- "mediagoblin.federation.object",
- objectType=self.objectType,
- id=self.id,
- qualified=True
- ),
+ "href": href,
},
}
@@ -476,20 +573,40 @@ class MediaEntry(Base, MediaEntryMixin):
if self.license:
context["license"] = self.license
+ if self.location:
+ context["location"] = self.get_location.serialize(request)
+
if show_comments:
- comments = [comment.serialize(request) for comment in self.get_comments()]
+ comments = [
+ comment.serialize(request) for comment in self.get_comments()]
total = len(comments)
context["replies"] = {
"totalItems": total,
"items": comments,
"url": request.urlgen(
- "mediagoblin.federation.object.comments",
- objectType=self.objectType,
+ "mediagoblin.api.object.comments",
+ object_type=self.object_type,
id=self.id,
qualified=True
),
}
+ # Add image height and width if possible. We didn't use to store this
+ # data and we're not able (and maybe not willing) to re-process all
+ # images so it's possible this might not exist.
+ if self.get_file_metadata("thumb", "height"):
+ height = self.get_file_metadata("thumb", "height")
+ context["image"]["height"] = height
+ if self.get_file_metadata("thumb", "width"):
+ width = self.get_file_metadata("thumb", "width")
+ context["image"]["width"] = width
+ if self.get_file_metadata("original", "height"):
+ height = self.get_file_metadata("original", "height")
+ context["fullImage"]["height"] = height
+ if self.get_file_metadata("original", "height"):
+ width = self.get_file_metadata("original", "width")
+ context["fullImage"]["width"] = width
+
return context
def unserialize(self, data):
@@ -503,6 +620,9 @@ class MediaEntry(Base, MediaEntryMixin):
if "license" in data:
self.license = data["license"]
+ if "location" in data:
+ Licence.create(data["location"], self)
+
return True
class FileKeynames(Base):
@@ -561,7 +681,7 @@ class MediaAttachmentFile(Base):
nullable=False)
name = Column(Unicode, nullable=False)
filepath = Column(PathTupleWithSlashes)
- created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
@property
def dict_view(self):
@@ -595,7 +715,7 @@ class MediaTag(Base):
nullable=False, index=True)
tag = Column(Integer, ForeignKey(Tag.id), nullable=False, index=True)
name = Column(Unicode)
- # created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+ # created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
__table_args__ = (
UniqueConstraint('tag', 'media_entry'),
@@ -626,8 +746,10 @@ class MediaComment(Base, MediaCommentMixin):
media_entry = Column(
Integer, ForeignKey(MediaEntry.id), nullable=False, index=True)
author = Column(Integer, ForeignKey(User.id), nullable=False)
- created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
content = Column(UnicodeText, nullable=False)
+ location = Column(Integer, ForeignKey("core__locations.id"))
+ get_location = relationship("Location", lazy="joined")
# Cascade: Comments are owned by their creator. So do the full thing.
# lazy=dynamic: People might post a *lot* of comments,
@@ -650,44 +772,60 @@ class MediaComment(Base, MediaCommentMixin):
lazy="dynamic",
cascade="all, delete-orphan"))
+
+ activity = Column(Integer, ForeignKey("core__activity_intermediators.id"))
+
def serialize(self, request):
""" Unserialize to python dictionary for API """
+ href = request.urlgen(
+ "mediagoblin.api.object",
+ object_type=self.object_type,
+ id=self.id,
+ qualified=True
+ )
media = MediaEntry.query.filter_by(id=self.media_entry).first()
author = self.get_author
+ published = UTC.localize(self.created)
context = {
- "id": self.id,
- "objectType": "comment",
+ "id": href,
+ "objectType": self.object_type,
"content": self.content,
"inReplyTo": media.serialize(request, show_comments=False),
- "author": author.serialize(request)
+ "author": author.serialize(request),
+ "published": published.isoformat(),
+ "updated": published.isoformat(),
}
+ if self.location:
+ context["location"] = self.get_location.seralize(request)
+
return context
- def unserialize(self, data):
+ def unserialize(self, data, request):
""" Takes API objects and unserializes on existing comment """
- # Do initial checks to verify the object is correct
- required_attributes = ["content", "inReplyTo"]
- for attr in required_attributes:
- if attr not in data:
+ # Handle changing the reply ID
+ if "inReplyTo" in data:
+ # Validate that the ID is correct
+ try:
+ media_id = int(extract_url_arguments(
+ url=data["inReplyTo"]["id"],
+ urlmap=request.app.url_map
+ )["id"])
+ except ValueError:
return False
- # Validate inReplyTo has ID
- if "id" not in data["inReplyTo"]:
- return False
+ media = MediaEntry.query.filter_by(id=media_id).first()
+ if media is None:
+ return False
- # Validate that the ID is correct
- try:
- media_id = int(data["inReplyTo"]["id"])
- except ValueError:
- return False
+ self.media_entry = media.id
- media = MediaEntry.query.filter_by(id=media_id).first()
- if media is None:
- return False
+ if "content" in data:
+ self.content = data["content"]
+
+ if "location" in data:
+ Location.create(data["location"], self)
- self.media_entry = media.id
- self.content = data["content"]
return True
@@ -702,10 +840,13 @@ class Collection(Base, CollectionMixin):
id = Column(Integer, primary_key=True)
title = Column(Unicode, nullable=False)
slug = Column(Unicode)
- created = Column(DateTime, nullable=False, default=datetime.datetime.now,
+ created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow,
index=True)
description = Column(UnicodeText)
creator = Column(Integer, ForeignKey(User.id), nullable=False)
+ location = Column(Integer, ForeignKey("core__locations.id"))
+ get_location = relationship("Location", lazy="joined")
+
# TODO: No of items in Collection. Badly named, can we migrate to num_items?
items = Column(Integer, default=0)
@@ -714,6 +855,8 @@ class Collection(Base, CollectionMixin):
backref=backref("collections",
cascade="all, delete-orphan"))
+ activity = Column(Integer, ForeignKey("core__activity_intermediators.id"))
+
__table_args__ = (
UniqueConstraint('creator', 'slug'),
{})
@@ -734,6 +877,18 @@ class Collection(Base, CollectionMixin):
creator=self.creator,
title=safe_title)
+ def serialize(self, request):
+ # Get all serialized output in a list
+ items = []
+ for item in self.get_collection_items():
+ items.append(item.serialize(request))
+
+ return {
+ "totalItems": self.items,
+ "url": self.url_for_self(request.urlgen, qualified=True),
+ "items": items,
+ }
+
class CollectionItem(Base, CollectionItemMixin):
__tablename__ = "core__collection_items"
@@ -743,7 +898,7 @@ class CollectionItem(Base, CollectionItemMixin):
Integer, ForeignKey(MediaEntry.id), nullable=False, index=True)
collection = Column(Integer, ForeignKey(Collection.id), nullable=False)
note = Column(UnicodeText, nullable=True)
- added = Column(DateTime, nullable=False, default=datetime.datetime.now)
+ added = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
position = Column(Integer)
# Cascade: CollectionItems are owned by their Collection. So do the full thing.
@@ -770,6 +925,9 @@ class CollectionItem(Base, CollectionItemMixin):
collection=self.collection,
entry=self.media_entry)
+ def serialize(self, request):
+ return self.get_media_entry.serialize(request)
+
class ProcessingMetaData(Base):
__tablename__ = 'core__processing_metadata'
@@ -792,7 +950,7 @@ class CommentSubscription(Base):
__tablename__ = 'core__comment_subscriptions'
id = Column(Integer, primary_key=True)
- created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
media_entry = relationship(MediaEntry,
@@ -823,7 +981,7 @@ class Notification(Base):
id = Column(Integer, primary_key=True)
type = Column(Unicode)
- created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
user_id = Column(Integer, ForeignKey('core__users.id'), nullable=False,
index=True)
@@ -883,9 +1041,8 @@ class ProcessingNotification(Notification):
'polymorphic_identity': 'processing_notification'
}
-with_polymorphic(
- Notification,
- [ProcessingNotification, CommentNotification])
+# the with_polymorphic call has been moved to the bottom above MODELS
+# this is because it causes conflicts with relationship calls.
class ReportBase(Base):
"""
@@ -930,7 +1087,7 @@ class ReportBase(Base):
lazy="dynamic",
cascade="all, delete-orphan"),
primaryjoin="User.id==ReportBase.reported_user_id")
- created = Column(DateTime, nullable=False, default=datetime.datetime.now())
+ created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
discriminator = Column('type', Unicode(50))
resolver_id = Column(Integer, ForeignKey(User.id))
resolver = relationship(
@@ -1068,13 +1225,198 @@ class PrivilegeUserAssociation(Base):
ForeignKey(Privilege.id),
primary_key=True)
+class Generator(Base):
+ """ Information about what created an activity """
+ __tablename__ = "core__generators"
+
+ id = Column(Integer, primary_key=True)
+ name = Column(Unicode, nullable=False)
+ published = Column(DateTime, default=datetime.datetime.utcnow)
+ updated = Column(DateTime, default=datetime.datetime.utcnow)
+ object_type = Column(Unicode, nullable=False)
+
+ def __repr__(self):
+ return "<{klass} {name}>".format(
+ klass=self.__class__.__name__,
+ name=self.name
+ )
+
+ def serialize(self, request):
+ href = request.urlgen(
+ "mediagoblin.api.object",
+ object_type=self.object_type,
+ id=self.id,
+ qualified=True
+ )
+ published = UTC.localize(self.published)
+ updated = UTC.localize(self.updated)
+ return {
+ "id": href,
+ "displayName": self.name,
+ "published": published.isoformat(),
+ "updated": updated.isoformat(),
+ "objectType": self.object_type,
+ }
+
+ def unserialize(self, data):
+ if "displayName" in data:
+ self.name = data["displayName"]
+
+
+class ActivityIntermediator(Base):
+ """
+ This is used so that objects/targets can have a foreign key back to this
+ object and activities can a foreign key to this object. This objects to be
+ used multiple times for the activity object or target and also allows for
+ different types of objects to be used as an Activity.
+ """
+ __tablename__ = "core__activity_intermediators"
+
+ id = Column(Integer, primary_key=True)
+ type = Column(Unicode, nullable=False)
+
+ TYPES = {
+ "user": User,
+ "media": MediaEntry,
+ "comment": MediaComment,
+ "collection": Collection,
+ }
+
+ def _find_model(self, obj):
+ """ Finds the model for a given object """
+ for key, model in self.TYPES.items():
+ if isinstance(obj, model):
+ return key, model
+
+ return None, None
+
+ def set(self, obj):
+ """ This sets itself as the activity """
+ key, model = self._find_model(obj)
+ if key is None:
+ raise ValueError("Invalid type of object given")
+
+ self.type = key
+
+ # We need to populate the self.id so we need to save but, we don't
+ # want to save this AI in the database (yet) so commit=False.
+ self.save(commit=False)
+ obj.activity = self.id
+ obj.save()
+
+ def get(self):
+ """ Finds the object for an activity """
+ if self.type is None:
+ return None
+
+ model = self.TYPES[self.type]
+ return model.query.filter_by(activity=self.id).first()
+
+ @validates("type")
+ def validate_type(self, key, value):
+ """ Validate that the type set is a valid type """
+ assert value in self.TYPES
+ return value
+
+class Activity(Base, ActivityMixin):
+ """
+ This holds all the metadata about an activity such as uploading an image,
+ posting a comment, etc.
+ """
+ __tablename__ = "core__activities"
+
+ id = Column(Integer, primary_key=True)
+ actor = Column(Integer,
+ ForeignKey("core__users.id"),
+ nullable=False)
+ published = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
+ updated = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
+ verb = Column(Unicode, nullable=False)
+ content = Column(Unicode, nullable=True)
+ title = Column(Unicode, nullable=True)
+ generator = Column(Integer,
+ ForeignKey("core__generators.id"),
+ nullable=True)
+ object = Column(Integer,
+ ForeignKey("core__activity_intermediators.id"),
+ nullable=False)
+ target = Column(Integer,
+ ForeignKey("core__activity_intermediators.id"),
+ nullable=True)
+
+ get_actor = relationship(User,
+ backref=backref("activities",
+ cascade="all, delete-orphan"))
+ get_generator = relationship(Generator)
+
+ def __repr__(self):
+ if self.content is None:
+ return "<{klass} verb:{verb}>".format(
+ klass=self.__class__.__name__,
+ verb=self.verb
+ )
+ else:
+ return "<{klass} {content}>".format(
+ klass=self.__class__.__name__,
+ content=self.content
+ )
+
+ @property
+ def get_object(self):
+ if self.object is None:
+ return None
+
+ ai = ActivityIntermediator.query.filter_by(id=self.object).first()
+ return ai.get()
+
+ def set_object(self, obj):
+ self.object = self._set_model(obj)
+
+ @property
+ def get_target(self):
+ if self.target is None:
+ return None
+
+ ai = ActivityIntermediator.query.filter_by(id=self.target).first()
+ return ai.get()
+
+ def set_target(self, obj):
+ self.target = self._set_model(obj)
+
+ def _set_model(self, obj):
+ # Firstly can we set obj
+ if not hasattr(obj, "activity"):
+ raise ValueError(
+ "{0!r} is unable to be set on activity".format(obj))
+
+ if obj.activity is None:
+ # We need to create a new AI
+ ai = ActivityIntermediator()
+ ai.set(obj)
+ ai.save()
+ return ai.id
+
+ # Okay we should have an existing AI
+ return ActivityIntermediator.query.filter_by(id=obj.activity).first().id
+
+ def save(self, set_updated=True, *args, **kwargs):
+ if set_updated:
+ self.updated = datetime.datetime.now()
+ super(Activity, self).save(*args, **kwargs)
+
+with_polymorphic(
+ Notification,
+ [ProcessingNotification, CommentNotification])
+
MODELS = [
User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem,
MediaFile, FileKeynames, MediaAttachmentFile, ProcessingMetaData,
Notification, CommentNotification, ProcessingNotification, Client,
CommentSubscription, ReportBase, CommentReport, MediaReport, UserBan,
Privilege, PrivilegeUserAssociation,
- RequestToken, AccessToken, NonceTimestamp]
+ RequestToken, AccessToken, NonceTimestamp,
+ Activity, ActivityIntermediator, Generator,
+ Location]
"""
Foundations are the default rows that are created immediately after the tables
@@ -1125,7 +1467,7 @@ def show_table_init(engine_uri):
if __name__ == '__main__':
from sys import argv
- print repr(argv)
+ print(repr(argv))
if len(argv) == 2:
uri = argv[1]
else: