Comment changes for federation

This adds a new Comment link table that is used to link between some
object and then the comment object, which can be more or less any
object in Mediagoblin. The MediaComment has been renamed to
TextComment as that more aptly describes what it is. There is
migrations for these changes.

There is also the conslidation of the Report tables into a single
Report table, the same with the Notification objects. This is because
both of them split out MediaEntry and Comment versions into their own
polymorphic versions from a base, this is no longer a meaningful
distinction as comments can be anything.
This commit is contained in:
Jessica Tallon 2015-10-20 12:24:54 +00:00
parent fd703bb4d0
commit 64a456a4e5
26 changed files with 760 additions and 350 deletions

View File

@ -22,7 +22,7 @@ from werkzeug.datastructures import FileStorage
from mediagoblin.decorators import oauth_required, require_active_login from mediagoblin.decorators import oauth_required, require_active_login
from mediagoblin.api.decorators import user_has_privilege from mediagoblin.api.decorators import user_has_privilege
from mediagoblin.db.models import User, LocalUser, MediaEntry, MediaComment, Activity from mediagoblin.db.models import User, LocalUser, MediaEntry, Comment, TextComment, Activity
from mediagoblin.tools.federation import create_activity, create_generator from mediagoblin.tools.federation import create_activity, create_generator
from mediagoblin.tools.routing import extract_url_arguments from mediagoblin.tools.routing import extract_url_arguments
from mediagoblin.tools.response import redirect, json_response, json_error, \ from mediagoblin.tools.response import redirect, json_response, json_error, \
@ -268,7 +268,7 @@ def feed_endpoint(request, outbox=None):
status=403 status=403
) )
comment = MediaComment(actor=request.user.id) comment = TextComment(actor=request.user.id)
comment.unserialize(data["object"], request) comment.unserialize(data["object"], request)
comment.save() comment.save()
@ -278,7 +278,7 @@ def feed_endpoint(request, outbox=None):
verb="post", verb="post",
actor=request.user, actor=request.user,
obj=comment, obj=comment,
target=comment.get_entry, target=comment.get_reply_to(),
generator=generator generator=generator
) )
@ -286,12 +286,22 @@ def feed_endpoint(request, outbox=None):
elif obj.get("objectType", None) == "image": elif obj.get("objectType", None) == "image":
# Posting an image to the feed # Posting an image to the feed
media_id = int(extract_url_arguments( media_id = extract_url_arguments(
url=data["object"]["id"], url=data["object"]["id"],
urlmap=request.app.url_map urlmap=request.app.url_map
)["id"]) )["id"]
media = MediaEntry.query.filter_by(id=media_id).first() # Build public_id
public_id = request.urlgen(
"mediagoblin.api.object",
object_type=obj["objectType"],
id=media_id,
qualified=True
)
media = MediaEntry.query.filter_by(
public_id=public_id
).first()
if media is None: if media is None:
return json_response( return json_response(
@ -345,10 +355,17 @@ def feed_endpoint(request, outbox=None):
if "id" not in obj: if "id" not in obj:
return json_error("Object ID has not been specified.") return json_error("Object ID has not been specified.")
obj_id = int(extract_url_arguments( obj_id = extract_url_arguments(
url=obj["id"], url=obj["id"],
urlmap=request.app.url_map urlmap=request.app.url_map
)["id"]) )["id"]
public_id = request.urlgen(
"mediagoblin.api.object",
object_type=obj["objectType"],
id=obj_id,
qualified=True
)
# Now try and find object # Now try and find object
if obj["objectType"] == "comment": if obj["objectType"] == "comment":
@ -358,7 +375,9 @@ def feed_endpoint(request, outbox=None):
status=403 status=403
) )
comment = MediaComment.query.filter_by(id=obj_id).first() comment = TextComment.query.filter_by(
public_id=public_id
).first()
if comment is None: if comment is None:
return json_error( return json_error(
"No such 'comment' with id '{0}'.".format(obj_id) "No such 'comment' with id '{0}'.".format(obj_id)
@ -391,7 +410,9 @@ def feed_endpoint(request, outbox=None):
return json_response(activity.serialize(request)) return json_response(activity.serialize(request))
elif obj["objectType"] == "image": elif obj["objectType"] == "image":
image = MediaEntry.query.filter_by(id=obj_id).first() image = MediaEntry.query.filter_by(
public_id=public_id
).first()
if image is None: if image is None:
return json_error( return json_error(
"No such 'image' with the id '{0}'.".format(obj["id"]) "No such 'image' with the id '{0}'.".format(obj["id"])
@ -454,15 +475,22 @@ def feed_endpoint(request, outbox=None):
return json_error("Object ID has not been specified.") return json_error("Object ID has not been specified.")
# Parse out the object ID # Parse out the object ID
obj_id = int(extract_url_arguments( obj_id = extract_url_arguments(
url=obj["id"], url=obj["id"],
urlmap=request.app.url_map urlmap=request.app.url_map
)["id"]) )["id"]
public_id = request.urlgen(
"mediagoblin.api.object",
object_type=obj["objectType"],
id=obj_id,
qualified=True
)
if obj.get("objectType", None) == "comment": if obj.get("objectType", None) == "comment":
# Find the comment asked for # Find the comment asked for
comment = MediaComment.query.filter_by( comment = TextComment.query.filter_by(
id=obj_id, public_id=public_id,
actor=request.user.id actor=request.user.id
).first() ).first()
@ -491,7 +519,7 @@ def feed_endpoint(request, outbox=None):
if obj.get("objectType", None) == "image": if obj.get("objectType", None) == "image":
# Find the image # Find the image
entry = MediaEntry.query.filter_by( entry = MediaEntry.query.filter_by(
id=obj_id, public_id=public_id,
actor=request.user.id actor=request.user.id
).first() ).first()
@ -500,10 +528,6 @@ def feed_endpoint(request, outbox=None):
"No such 'image' with id '{0}'.".format(obj_id) "No such 'image' with id '{0}'.".format(obj_id)
) )
# Okay lets do our best to ensure there is a public_id for
# this image, there most likely is but it's important!
entry.get_public_id(request.urlgen)
# Make the delete activity # Make the delete activity
generator = create_generator(request) generator = create_generator(request)
activity = create_activity( activity = create_activity(
@ -621,7 +645,14 @@ def object_endpoint(request):
status=404 status=404
) )
media = MediaEntry.query.filter_by(id=object_id).first() public_id = request.urlgen(
"mediagoblin.api.object",
object_type=object_type,
id=object_id,
qualified=True
)
media = MediaEntry.query.filter_by(public_id=public_id).first()
if media is None: if media is None:
return json_error( return json_error(
"Can't find '{0}' with ID '{1}'".format(object_type, object_id), "Can't find '{0}' with ID '{1}'".format(object_type, object_id),
@ -633,7 +664,13 @@ def object_endpoint(request):
@oauth_required @oauth_required
def object_comments(request): def object_comments(request):
""" Looks up for the comments on a object """ """ Looks up for the comments on a object """
media = MediaEntry.query.filter_by(id=request.matchdict["id"]).first() public_id = request.urlgen(
"mediagoblin.api.object",
object_type=request.matchdict["object_type"],
id=request.matchdict["id"],
qualified=True
)
media = MediaEntry.query.filter_by(public_id=public_id).first()
if media is None: if media is None:
return json_error("Can't find '{0}' with ID '{1}'".format( return json_error("Can't find '{0}' with ID '{1}'".format(
request.matchdict["object_type"], request.matchdict["object_type"],

View File

@ -13,6 +13,9 @@
# #
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import six
import copy
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import inspect from sqlalchemy import inspect
@ -22,6 +25,30 @@ if not DISABLE_GLOBALS:
from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.orm import scoped_session, sessionmaker
Session = scoped_session(sessionmaker()) Session = scoped_session(sessionmaker())
class FakeCursor(object):
def __init__ (self, cursor, mapper, filter=None):
self.cursor = cursor
self.mapper = mapper
self.filter = filter
def count(self):
return self.cursor.count()
def __copy__(self):
# Or whatever the function is named to make
# copy.copy happy?
return FakeCursor(copy.copy(self.cursor), self.mapper, self.filter)
def __iter__(self):
return six.moves.filter(self.filter, six.moves.map(self.mapper, self.cursor))
def __getitem__(self, key):
return self.mapper(self.cursor[key])
def slice(self, *args, **kwargs):
r = self.cursor.slice(*args, **kwargs)
return list(six.moves.filter(self.filter, six.moves.map(self.mapper, r)))
class GMGTableBase(object): class GMGTableBase(object):
# Deletion types # Deletion types
@ -93,7 +120,7 @@ class GMGTableBase(object):
id=self.actor id=self.actor
).first() ).first()
tombstone.object_type = self.object_type tombstone.object_type = self.object_type
tombstone.save() tombstone.save(commit=False)
# There will be a lot of places where the GenericForeignKey will point # There will be a lot of places where the GenericForeignKey will point
# to the model, we want to remap those to our tombstone. # to the model, we want to remap those to our tombstone.

View File

@ -37,7 +37,7 @@ from mediagoblin.tools import crypto
from mediagoblin.db.extratypes import JSONEncoded, MutationDict from mediagoblin.db.extratypes import JSONEncoded, MutationDict
from mediagoblin.db.migration_tools import ( from mediagoblin.db.migration_tools import (
RegisterMigration, inspect_table, replace_table_hack) RegisterMigration, inspect_table, replace_table_hack)
from mediagoblin.db.models import (MediaEntry, Collection, MediaComment, User, from mediagoblin.db.models import (MediaEntry, Collection, Comment, User,
Privilege, Generator, LocalUser, Location, Privilege, Generator, LocalUser, Location,
Client, RequestToken, AccessToken) Client, RequestToken, AccessToken)
from mediagoblin.db.extratypes import JSONEncoded, MutationDict from mediagoblin.db.extratypes import JSONEncoded, MutationDict
@ -353,7 +353,7 @@ class CommentNotification_v0(Notification_v0):
__tablename__ = 'core__comment_notifications' __tablename__ = 'core__comment_notifications'
id = Column(Integer, ForeignKey(Notification_v0.id), primary_key=True) id = Column(Integer, ForeignKey(Notification_v0.id), primary_key=True)
subject_id = Column(Integer, ForeignKey(MediaComment.id)) subject_id = Column(Integer, ForeignKey(Comment.id))
class ProcessingNotification_v0(Notification_v0): class ProcessingNotification_v0(Notification_v0):
@ -542,7 +542,7 @@ class CommentReport_v0(ReportBase_v0):
id = Column('id',Integer, ForeignKey('core__reports.id'), id = Column('id',Integer, ForeignKey('core__reports.id'),
primary_key=True) primary_key=True)
comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=True) comment_id = Column(Integer, ForeignKey(Comment.id), nullable=True)
class MediaReport_v0(ReportBase_v0): class MediaReport_v0(ReportBase_v0):
@ -917,7 +917,7 @@ class ActivityIntermediator_R0(declarative_base()):
TYPES = { TYPES = {
"user": User, "user": User,
"media": MediaEntry, "media": MediaEntry,
"comment": MediaComment, "comment": Comment,
"collection": Collection, "collection": Collection,
} }
@ -1875,3 +1875,268 @@ def add_public_id(db):
# Commit this. # Commit this.
db.commit() db.commit()
class Comment_V0(declarative_base()):
__tablename__ = "core__comment_links"
id = Column(Integer, primary_key=True)
target_id = Column(
Integer,
ForeignKey(GenericModelReference_V0.id),
nullable=False
)
comment_id = Column(
Integer,
ForeignKey(GenericModelReference_V0.id),
nullable=False
)
added = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
@RegisterMigration(41, MIGRATIONS)
def federation_comments(db):
"""
This reworks the MediaComent to be a more generic Comment model.
"""
metadata = MetaData(bind=db.bind)
textcomment_table = inspect_table(metadata, "core__media_comments")
gmr_table = inspect_table(metadata, "core__generic_model_reference")
# First of all add the public_id field to the TextComment table
comment_public_id_column = Column(
"public_id",
Unicode,
unique=True
)
comment_public_id_column.create(
textcomment_table,
unique_name="public_id_unique"
)
comment_updated_column = Column(
"updated",
DateTime,
)
comment_updated_column.create(textcomment_table)
# First create the Comment link table.
Comment_V0.__table__.create(db.bind)
db.commit()
# now look up the comment table
comment_table = inspect_table(metadata, "core__comment_links")
# Itierate over all the comments and add them to the link table.
for comment in db.execute(textcomment_table.select()):
# Check if there is a GMR to the comment.
comment_gmr = db.execute(gmr_table.select().where(and_(
gmr_table.c.obj_pk == comment.id,
gmr_table.c.model_type == "core__media_comments"
))).first()
if comment_gmr:
comment_gmr = comment_gmr[0]
else:
comment_gmr = db.execute(gmr_table.insert().values(
obj_pk=comment.id,
model_type="core__media_comments"
)).inserted_primary_key[0]
# Get or create the GMR for the media entry
entry_gmr = db.execute(gmr_table.select().where(and_(
gmr_table.c.obj_pk == comment.media_entry,
gmr_table.c.model_type == "core__media_entries"
))).first()
if entry_gmr:
entry_gmr = entry_gmr[0]
else:
entry_gmr = db.execute(gmr_table.insert().values(
obj_pk=comment.media_entry,
model_type="core__media_entries"
)).inserted_primary_key[0]
# Add the comment link.
db.execute(comment_table.insert().values(
target_id=entry_gmr,
comment_id=comment_gmr,
added=datetime.datetime.utcnow()
))
# Add the data to the updated field
db.execute(textcomment_table.update().where(
textcomment_table.c.id == comment.id
).values(
updated=comment.created
))
db.commit()
# Add not null constraint
textcomment_update_column = textcomment_table.columns["updated"]
textcomment_update_column.alter(nullable=False)
# Remove the unused fields on the TextComment model
comment_media_entry_column = textcomment_table.columns["media_entry"]
comment_media_entry_column.drop()
db.commit()
@RegisterMigration(42, MIGRATIONS)
def consolidate_reports(db):
""" Consolidates the report tables into just one """
metadata = MetaData(bind=db.bind)
report_table = inspect_table(metadata, "core__reports")
comment_report_table = inspect_table(metadata, "core__reports_on_comments")
media_report_table = inspect_table(metadata, "core__reports_on_media")
gmr_table = inspect_table(metadata, "core__generic_model_reference")
# Add the GMR object field onto the base report table
report_object_id_column = Column(
"object_id",
Integer,
ForeignKey(GenericModelReference_V0.id),
)
report_object_id_column.create(report_table)
db.commit()
# Iterate through the reports in the comment table and merge them in.
for comment_report in db.execute(comment_report_table.select()):
# Find a GMR for this if one exists.
crgmr = db.execute(gmr_table.select().where(and_(
gmr_table.c.obj_pk == comment_report.comment_id,
gmr_table.c.model_type == "core__media_comments"
))).first()
if crgmr:
crgmr = crgmr[0]
else:
crgmr = db.execute(gmr_table.insert().values(
gmr_table.c.obj_pk == comment_report.comment_id,
gmr_table.c.model_type == "core__media_comments"
)).inserted_primary_key[0]
# Great now we can save this back onto the (base) report.
db.execute(report_table.update().where(
report_table.c.id == comment_report.id
).values(
object_id=crgmr
))
# Iterate through the Media Reports and do the save as above.
for media_report in db.execute(media_report_table.select()):
# Find Mr. GMR :)
mrgmr = db.execute(gmr_table.select().where(and_(
gmr_table.c.obj_pk == media_report.media_entry_id,
gmr_table.c.model_type == "core__media_entries"
))).first()
if mrgmr:
mrgmr = mrgmr[0]
else:
mrgmr = db.execute(gmr_table.insert().values(
obj_pk=media_report.media_entry_id,
model_type="core__media_entries"
)).inserted_primary_key[0]
# Save back on to the base.
db.execute(report_table.update().where(
report_table.c.id == media_report.id
).values(
object_id=mrgmr
))
db.commit()
# Add the not null constraint
report_object_id = report_table.columns["object_id"]
report_object_id.alter(nullable=False)
# Now we can remove the fields we don't need anymore
report_type = report_table.columns["type"]
report_type.drop()
# Drop both MediaReports and CommentTable.
comment_report_table.drop()
media_report_table.drop()
# Commit we're done.
db.commit()
@RegisterMigration(43, MIGRATIONS)
def consolidate_notification(db):
""" Consolidates the notification models into one """
metadata = MetaData(bind=db.bind)
notification_table = inspect_table(metadata, "core__notifications")
cn_table = inspect_table(metadata, "core__comment_notifications")
cp_table = inspect_table(metadata, "core__processing_notifications")
gmr_table = inspect_table(metadata, "core__generic_model_reference")
# Add fields needed
notification_object_id_column = Column(
"object_id",
Integer,
ForeignKey(GenericModelReference_V0.id)
)
notification_object_id_column.create(notification_table)
db.commit()
# Iterate over comments and move to notification base table.
for comment_notification in db.execute(cn_table.select()):
# Find the GMR.
cngmr = db.execute(gmr_table.select().where(and_(
gmr_table.c.obj_pk == comment_notification.subject_id,
gmr_table.c.model_type == "core__media_comments"
))).first()
if cngmr:
cngmr = cngmr[0]
else:
cngmr = db.execute(gmr_table.insert().values(
obj_pk=comment_notification.subject_id,
model_type="core__media_comments"
)).inserted_primary_key[0]
# Save back on notification
db.execute(notification_table.update().where(
notification_table.c.id == comment_notification.id
).values(
object_id=cngmr
))
db.commit()
# Do the same for processing notifications
for processing_notification in db.execute(cp_table.select()):
cpgmr = db.execute(gmr_table.select().where(and_(
gmr_table.c.obj_pk == processing_notification.subject_id,
gmr_table.c.model_type == "core__processing_notifications"
))).first()
if cpgmr:
cpgmr = cpgmr[0]
else:
cpgmr = db.execute(gmr_table.insert().values(
obj_pk=processing_notification.subject_id,
model_type="core__processing_notifications"
)).inserted_primary_key[0]
db.execute(notification_table.update().where(
notification_table.c.id == processing_notification.id
).values(
object_id=cpgmr
))
db.commit()
# Add the not null constraint
notification_object_id = notification_table.columns["object_id"]
notification_object_id.alter(nullable=False)
# Now drop the fields we don't need
notification_type_column = notification_table.columns["type"]
notification_type_column.drop()
# Drop the tables we no longer need
cp_table.drop()
cn_table.drop()
db.commit()

View File

@ -41,6 +41,47 @@ from mediagoblin.tools.text import cleaned_markdown_conversion
from mediagoblin.tools.url import slugify from mediagoblin.tools.url import slugify
from mediagoblin.tools.translate import pass_to_ugettext as _ from mediagoblin.tools.translate import pass_to_ugettext as _
class CommentingMixin(object):
"""
Mixin that gives classes methods to get and add the comments on/to it
This assumes the model has a "comments" class which is a ForeignKey to the
Collection model. This will hold a Collection of comments which are
associated to this model. It also assumes the model has an "actor"
ForeignKey which points to the creator/publisher/etc. of the model.
NB: This is NOT the mixin for the Comment Model, this is for
other models which support commenting.
"""
def get_comment_link(self):
# Import here to avoid cyclic imports
from mediagoblin.db.models import Comment, GenericModelReference
gmr = GenericModelReference.query.filter_by(
obj_pk=self.id,
model_type=self.__tablename__
).first()
if gmr is None:
return None
link = Comment.query.filter_by(comment_id=gmr.id).first()
return link
def get_reply_to(self):
link = self.get_comment_link()
if link is None or link.target_id is None:
return None
return link.target()
def soft_delete(self, *args, **kwargs):
link = self.get_comment_link()
if link is not None:
link.delete()
super(CommentingMixin, self).soft_delete(*args, **kwargs)
class GeneratePublicIDMixin(object): class GeneratePublicIDMixin(object):
""" """
Mixin that ensures that a the public_id field is populated. Mixin that ensures that a the public_id field is populated.
@ -71,9 +112,10 @@ class GeneratePublicIDMixin(object):
self.public_id = urlgen( self.public_id = urlgen(
"mediagoblin.api.object", "mediagoblin.api.object",
object_type=self.object_type, object_type=self.object_type,
id=self.id, id=str(uuid.uuid4()),
qualified=True qualified=True
) )
self.save()
return self.public_id return self.public_id
class UserMixin(object): class UserMixin(object):
@ -342,7 +384,7 @@ class MediaEntryMixin(GenerateSlugMixin, GeneratePublicIDMixin):
return exif_short return exif_short
class MediaCommentMixin(object): class TextCommentMixin(GeneratePublicIDMixin):
object_type = "comment" object_type = "comment"
@property @property
@ -367,7 +409,6 @@ class MediaCommentMixin(object):
actor=self.get_actor, actor=self.get_actor,
comment=self.content) comment=self.content)
class CollectionMixin(GenerateSlugMixin, GeneratePublicIDMixin): class CollectionMixin(GenerateSlugMixin, GeneratePublicIDMixin):
object_type = "collection" object_type = "collection"
@ -404,6 +445,28 @@ class CollectionMixin(GenerateSlugMixin, GeneratePublicIDMixin):
collection=self.slug_or_id, collection=self.slug_or_id,
**extra_args) **extra_args)
def add_to_collection(self, obj, content=None, commit=True):
""" Adds an object to the collection """
# It's here to prevent cyclic imports
from mediagoblin.db.models import CollectionItem
# Need the ID of this collection for this so check we've got one.
self.save(commit=False)
# Create the CollectionItem
item = CollectionItem()
item.collection = self.id
item.get_object = obj
if content is not None:
item.note = content
self.num_items = self.num_items + 1
# Save both!
self.save(commit=commit)
item.save(commit=commit)
return item
class CollectionItemMixin(object): class CollectionItemMixin(object):
@property @property

View File

@ -36,10 +36,10 @@ from sqlalchemy.util import memoized_property
from mediagoblin.db.extratypes import (PathTupleWithSlashes, JSONEncoded, from mediagoblin.db.extratypes import (PathTupleWithSlashes, JSONEncoded,
MutationDict) MutationDict)
from mediagoblin.db.base import Base, DictReadAttrProxy from mediagoblin.db.base import Base, DictReadAttrProxy, FakeCursor
from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, \ from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, \
MediaCommentMixin, CollectionMixin, CollectionItemMixin, \ CollectionMixin, CollectionItemMixin, ActivityMixin, TextCommentMixin, \
ActivityMixin CommentingMixin
from mediagoblin.tools.files import delete_media_files from mediagoblin.tools.files import delete_media_files
from mediagoblin.tools.common import import_component from mediagoblin.tools.common import import_component
from mediagoblin.tools.routing import extract_url_arguments from mediagoblin.tools.routing import extract_url_arguments
@ -262,7 +262,7 @@ class User(Base, UserMixin):
collection.delete(**kwargs) collection.delete(**kwargs)
# Find all the comments and delete those too # Find all the comments and delete those too
for comment in MediaComment.query.filter_by(actor=self.id): for comment in TextComment.query.filter_by(actor=self.id):
comment.delete(**kwargs) comment.delete(**kwargs)
# Find all the activities and delete those too # Find all the activities and delete those too
@ -509,7 +509,7 @@ class NonceTimestamp(Base):
nonce = Column(Unicode, nullable=False, primary_key=True) nonce = Column(Unicode, nullable=False, primary_key=True)
timestamp = Column(DateTime, nullable=False, primary_key=True) timestamp = Column(DateTime, nullable=False, primary_key=True)
class MediaEntry(Base, MediaEntryMixin): class MediaEntry(Base, MediaEntryMixin, CommentingMixin):
""" """
TODO: Consider fetching the media_files using join TODO: Consider fetching the media_files using join
""" """
@ -595,11 +595,18 @@ class MediaEntry(Base, MediaEntryMixin):
)) ))
def get_comments(self, ascending=False): def get_comments(self, ascending=False):
order_col = MediaComment.created query = Comment.query.join(Comment.target_helper).filter(and_(
if not ascending: GenericModelReference.obj_pk == self.id,
order_col = desc(order_col) GenericModelReference.model_type == self.__tablename__
return self.all_comments.order_by(order_col) ))
if ascending:
query = query.order_by(Comment.added.asc())
else:
qury = query.order_by(Comment.added.desc())
return FakeCursor(query, lambda c:c.comment())
def url_to_prev(self, urlgen): def url_to_prev(self, urlgen):
"""get the next 'newer' entry by this user""" """get the next 'newer' entry by this user"""
media = MediaEntry.query.filter( media = MediaEntry.query.filter(
@ -689,7 +696,7 @@ class MediaEntry(Base, MediaEntryMixin):
def soft_delete(self, *args, **kwargs): def soft_delete(self, *args, **kwargs):
# Find all of the media comments for this and delete them # Find all of the media comments for this and delete them
for comment in MediaComment.query.filter_by(media_entry=self.id): for comment in self.get_comments():
comment.delete(*args, **kwargs) comment.delete(*args, **kwargs)
super(MediaEntry, self).soft_delete(*args, **kwargs) super(MediaEntry, self).soft_delete(*args, **kwargs)
@ -927,15 +934,63 @@ class MediaTag(Base):
"""A dict like view on this object""" """A dict like view on this object"""
return DictReadAttrProxy(self) return DictReadAttrProxy(self)
class Comment(Base):
"""
Link table between a response and another object that can have replies.
This acts as a link table between an object and the comments on it, it's
done like this so that you can look up all the comments without knowing
whhich comments are on an object before hand. Any object can be a comment
and more or less any object can accept comments too.
class MediaComment(Base, MediaCommentMixin): Important: This is NOT the old MediaComment table.
"""
__tablename__ = "core__comment_links"
id = Column(Integer, primary_key=True)
# The GMR to the object the comment is on.
target_id = Column(
Integer,
ForeignKey(GenericModelReference.id),
nullable=False
)
target_helper = relationship(
GenericModelReference,
foreign_keys=[target_id]
)
target = association_proxy("target_helper", "get_object",
creator=GenericModelReference.find_or_new)
# The comment object
comment_id = Column(
Integer,
ForeignKey(GenericModelReference.id),
nullable=False
)
comment_helper = relationship(
GenericModelReference,
foreign_keys=[comment_id]
)
comment = association_proxy("comment_helper", "get_object",
creator=GenericModelReference.find_or_new)
# When it was added
added = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
class TextComment(Base, TextCommentMixin, CommentingMixin):
"""
A basic text comment, this is a usually short amount of text and nothing else
"""
# This is a legacy from when Comments where just on MediaEntry objects.
__tablename__ = "core__media_comments" __tablename__ = "core__media_comments"
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
media_entry = Column( public_id = Column(Unicode, unique=True)
Integer, ForeignKey(MediaEntry.id), nullable=False, index=True)
actor = Column(Integer, ForeignKey(User.id), nullable=False) actor = Column(Integer, ForeignKey(User.id), nullable=False)
created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
updated = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
content = Column(UnicodeText, nullable=False) content = Column(UnicodeText, nullable=False)
location = Column(Integer, ForeignKey("core__locations.id")) location = Column(Integer, ForeignKey("core__locations.id"))
get_location = relationship("Location", lazy="joined") get_location = relationship("Location", lazy="joined")
@ -947,38 +1002,25 @@ class MediaComment(Base, MediaCommentMixin):
backref=backref("posted_comments", backref=backref("posted_comments",
lazy="dynamic", lazy="dynamic",
cascade="all, delete-orphan")) cascade="all, delete-orphan"))
get_entry = relationship(MediaEntry,
backref=backref("comments",
lazy="dynamic",
cascade="all, delete-orphan"))
# Cascade: Comments are somewhat owned by their MediaEntry.
# So do the full thing.
# lazy=dynamic: MediaEntries might have many comments,
# so make the "all_comments" a query-like thing.
get_media_entry = relationship(MediaEntry,
backref=backref("all_comments",
lazy="dynamic",
cascade="all, delete-orphan"))
deletion_mode = Base.SOFT_DELETE deletion_mode = Base.SOFT_DELETE
def serialize(self, request): def serialize(self, request):
""" Unserialize to python dictionary for API """ """ Unserialize to python dictionary for API """
href = request.urlgen( target = self.get_reply_to()
"mediagoblin.api.object", # If this is target just.. give them nothing?
object_type=self.object_type, if target is None:
id=self.id, target = {}
qualified=True else:
) target = target.serialize(request, show_comments=False)
media = MediaEntry.query.filter_by(id=self.media_entry).first()
author = self.get_actor author = self.get_actor
published = UTC.localize(self.created) published = UTC.localize(self.created)
context = { context = {
"id": href, "id": self.get_public_id(request.urlgen),
"objectType": self.object_type, "objectType": self.object_type,
"content": self.content, "content": self.content,
"inReplyTo": media.serialize(request, show_comments=False), "inReplyTo": target,
"author": author.serialize(request), "author": author.serialize(request),
"published": published.isoformat(), "published": published.isoformat(),
"updated": published.isoformat(), "updated": published.isoformat(),
@ -991,34 +1033,47 @@ class MediaComment(Base, MediaCommentMixin):
def unserialize(self, data, request): def unserialize(self, data, request):
""" Takes API objects and unserializes on existing comment """ """ Takes API objects and unserializes on existing comment """
# 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
media = MediaEntry.query.filter_by(id=media_id).first()
if media is None:
return False
self.media_entry = media.id
if "content" in data: if "content" in data:
self.content = data["content"] self.content = data["content"]
if "location" in data: if "location" in data:
Location.create(data["location"], self) Location.create(data["location"], self)
# Handle changing the reply ID
if "inReplyTo" in data:
# Validate that the ID is correct
try:
id = extract_url_arguments(
url=data["inReplyTo"]["id"],
urlmap=request.app.url_map
)["id"]
except ValueError:
raise False
public_id = request.urlgen(
"mediagoblin.api.object",
id=id,
object_type=data["inReplyTo"]["objectType"],
qualified=True
)
media = MediaEntry.query.filter_by(public_id=public_id).first()
if media is None:
return False
# We need an ID for this model.
self.save(commit=False)
# Create the link
link = Comment()
link.target = media
link.comment = self
link.save()
return True return True
class Collection(Base, CollectionMixin, CommentingMixin):
class Collection(Base, CollectionMixin):
"""A representation of a collection of objects. """A representation of a collection of objects.
This holds a group/collection of objects that could be a user defined album This holds a group/collection of objects that could be a user defined album
@ -1070,6 +1125,7 @@ class Collection(Base, CollectionMixin):
OUTBOX_TYPE = "core-outbox" OUTBOX_TYPE = "core-outbox"
FOLLOWER_TYPE = "core-followers" FOLLOWER_TYPE = "core-followers"
FOLLOWING_TYPE = "core-following" FOLLOWING_TYPE = "core-following"
COMMENT_TYPE = "core-comments"
USER_DEFINED_TYPE = "core-user-defined" USER_DEFINED_TYPE = "core-user-defined"
def get_collection_items(self, ascending=False): def get_collection_items(self, ascending=False):
@ -1201,21 +1257,19 @@ class CommentSubscription(Base):
class Notification(Base): class Notification(Base):
__tablename__ = 'core__notifications' __tablename__ = 'core__notifications'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
type = Column(Unicode)
object_id = Column(Integer, ForeignKey(GenericModelReference.id))
object_helper = relationship(GenericModelReference)
obj = association_proxy("object_helper", "get_object",
creator=GenericModelReference.find_or_new)
created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
user_id = Column(Integer, ForeignKey('core__users.id'), nullable=False, user_id = Column(Integer, ForeignKey('core__users.id'), nullable=False,
index=True) index=True)
seen = Column(Boolean, default=lambda: False, index=True) seen = Column(Boolean, default=lambda: False, index=True)
user = relationship( user = relationship(
User, User,
backref=backref('notifications', cascade='all, delete-orphan')) backref=backref('notifications', cascade='all, delete-orphan'))
__mapper_args__ = {
'polymorphic_identity': 'notification',
'polymorphic_on': type
}
def __repr__(self): def __repr__(self):
return '<{klass} #{id}: {user}: {subject} ({seen})>'.format( return '<{klass} #{id}: {user}: {subject} ({seen})>'.format(
@ -1233,42 +1287,9 @@ class Notification(Base):
subject=getattr(self, 'subject', None), subject=getattr(self, 'subject', None),
seen='unseen' if not self.seen else 'seen') seen='unseen' if not self.seen else 'seen')
class Report(Base):
class CommentNotification(Notification):
__tablename__ = 'core__comment_notifications'
id = Column(Integer, ForeignKey(Notification.id), primary_key=True)
subject_id = Column(Integer, ForeignKey(MediaComment.id))
subject = relationship(
MediaComment,
backref=backref('comment_notifications', cascade='all, delete-orphan'))
__mapper_args__ = {
'polymorphic_identity': 'comment_notification'
}
class ProcessingNotification(Notification):
__tablename__ = 'core__processing_notifications'
id = Column(Integer, ForeignKey(Notification.id), primary_key=True)
subject_id = Column(Integer, ForeignKey(MediaEntry.id))
subject = relationship(
MediaEntry,
backref=backref('processing_notifications',
cascade='all, delete-orphan'))
__mapper_args__ = {
'polymorphic_identity': 'processing_notification'
}
# the with_polymorphic call has been moved to the bottom above MODELS
# this is because it causes conflicts with relationship calls.
class ReportBase(Base):
""" """
This is the basic report object which the other reports are based off of. Represents a report that someone might file against Media, Comments, etc.
:keyword reporter_id Holds the id of the user who created :keyword reporter_id Holds the id of the user who created
the report, as an Integer column. the report, as an Integer column.
@ -1281,8 +1302,6 @@ class ReportBase(Base):
an Integer column. an Integer column.
:keyword created Holds a datetime column of when the re- :keyword created Holds a datetime column of when the re-
-port was filed. -port was filed.
:keyword discriminator This column distinguishes between the
different types of reports.
:keyword resolver_id Holds the id of the moderator/admin who :keyword resolver_id Holds the id of the moderator/admin who
resolved the report. resolved the report.
:keyword resolved Holds the DateTime object which descri- :keyword resolved Holds the DateTime object which descri-
@ -1291,8 +1310,11 @@ class ReportBase(Base):
resolver's reasons for resolving resolver's reasons for resolving
the report this way. Some of this the report this way. Some of this
is auto-generated is auto-generated
:keyword object_id Holds the ID of the GenericModelReference
which points to the reported object.
""" """
__tablename__ = 'core__reports' __tablename__ = 'core__reports'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
reporter_id = Column(Integer, ForeignKey(User.id), nullable=False) reporter_id = Column(Integer, ForeignKey(User.id), nullable=False)
reporter = relationship( reporter = relationship(
@ -1300,7 +1322,7 @@ class ReportBase(Base):
backref=backref("reports_filed_by", backref=backref("reports_filed_by",
lazy="dynamic", lazy="dynamic",
cascade="all, delete-orphan"), cascade="all, delete-orphan"),
primaryjoin="User.id==ReportBase.reporter_id") primaryjoin="User.id==Report.reporter_id")
report_content = Column(UnicodeText) report_content = Column(UnicodeText)
reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False) reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False)
reported_user = relationship( reported_user = relationship(
@ -1308,69 +1330,42 @@ class ReportBase(Base):
backref=backref("reports_filed_on", backref=backref("reports_filed_on",
lazy="dynamic", lazy="dynamic",
cascade="all, delete-orphan"), cascade="all, delete-orphan"),
primaryjoin="User.id==ReportBase.reported_user_id") primaryjoin="User.id==Report.reported_user_id")
created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
discriminator = Column('type', Unicode(50))
resolver_id = Column(Integer, ForeignKey(User.id)) resolver_id = Column(Integer, ForeignKey(User.id))
resolver = relationship( resolver = relationship(
User, User,
backref=backref("reports_resolved_by", backref=backref("reports_resolved_by",
lazy="dynamic", lazy="dynamic",
cascade="all, delete-orphan"), cascade="all, delete-orphan"),
primaryjoin="User.id==ReportBase.resolver_id") primaryjoin="User.id==Report.resolver_id")
resolved = Column(DateTime) resolved = Column(DateTime)
result = Column(UnicodeText) result = Column(UnicodeText)
__mapper_args__ = {'polymorphic_on': discriminator}
object_id = Column(Integer, ForeignKey(GenericModelReference.id), nullable=False)
def is_comment_report(self): object_helper = relationship(GenericModelReference)
return self.discriminator=='comment_report' obj = association_proxy("object_helper", "get_object",
creator=GenericModelReference.find_or_new)
def is_media_entry_report(self):
return self.discriminator=='media_report'
def is_archived_report(self): def is_archived_report(self):
return self.resolved is not None return self.resolved is not None
def is_comment_report(self):
if self.object_id is None:
return False
return isinstance(self.obj(), TextComment)
def is_media_entry_report(self):
if self.object_id is None:
return False
return isinstance(self.obj(), MediaEntry)
def archive(self,resolver_id, resolved, result): def archive(self,resolver_id, resolved, result):
self.resolver_id = resolver_id self.resolver_id = resolver_id
self.resolved = resolved self.resolved = resolved
self.result = result self.result = result
class CommentReport(ReportBase):
"""
Reports that have been filed on comments.
:keyword comment_id Holds the integer value of the reported
comment's ID
"""
__tablename__ = 'core__reports_on_comments'
__mapper_args__ = {'polymorphic_identity': 'comment_report'}
id = Column('id',Integer, ForeignKey('core__reports.id'),
primary_key=True)
comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=True)
comment = relationship(
MediaComment, backref=backref("reports_filed_on",
lazy="dynamic"))
class MediaReport(ReportBase):
"""
Reports that have been filed on media entries
:keyword media_entry_id Holds the integer value of the reported
media entry's ID
"""
__tablename__ = 'core__reports_on_media'
__mapper_args__ = {'polymorphic_identity': 'media_report'}
id = Column('id',Integer, ForeignKey('core__reports.id'),
primary_key=True)
media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=True)
media_entry = relationship(
MediaEntry,
backref=backref("reports_filed_on",
lazy="dynamic"))
class UserBan(Base): class UserBan(Base):
""" """
Holds the information on a specific user's ban-state. As long as one of Holds the information on a specific user's ban-state. As long as one of
@ -1576,18 +1571,12 @@ class Graveyard(Base):
"deleted": self.deleted "deleted": self.deleted
} }
with_polymorphic(
Notification,
[ProcessingNotification, CommentNotification])
MODELS = [ MODELS = [
LocalUser, RemoteUser, User, MediaEntry, Tag, MediaTag, MediaComment, LocalUser, RemoteUser, User, MediaEntry, Tag, MediaTag, Comment, TextComment,
Collection, CollectionItem, MediaFile, FileKeynames, MediaAttachmentFile, Collection, CollectionItem, MediaFile, FileKeynames, MediaAttachmentFile,
ProcessingMetaData, Notification, CommentNotification, ProcessingMetaData, Notification, Client, CommentSubscription, Report,
ProcessingNotification, Client, CommentSubscription, ReportBase, UserBan, Privilege, PrivilegeUserAssociation, RequestToken, AccessToken,
CommentReport, MediaReport, UserBan, Privilege, PrivilegeUserAssociation, NonceTimestamp, Activity, Generator, Location, GenericModelReference, Graveyard]
RequestToken, AccessToken, NonceTimestamp, Activity, Generator, Location,
GenericModelReference, Graveyard]
""" """
Foundations are the default rows that are created immediately after the tables Foundations are the default rows that are created immediately after the tables

View File

@ -23,7 +23,8 @@ from six.moves.urllib.parse import urljoin
from mediagoblin import mg_globals as mgg from mediagoblin import mg_globals as mgg
from mediagoblin import messages from mediagoblin import messages
from mediagoblin.db.models import MediaEntry, LocalUser, MediaComment, AccessToken from mediagoblin.db.models import MediaEntry, LocalUser, TextComment, \
AccessToken, Comment
from mediagoblin.tools.response import ( from mediagoblin.tools.response import (
redirect, render_404, redirect, render_404,
render_user_banned, json_response) render_user_banned, json_response)
@ -325,11 +326,11 @@ def allow_reporting(controller):
def get_optional_media_comment_by_id(controller): def get_optional_media_comment_by_id(controller):
""" """
Pass in a MediaComment based off of a url component. Because of this decor- Pass in a Comment based off of a url component. Because of this decor-
-ator's use in filing Media or Comment Reports, it has two valid outcomes. -ator's use in filing Reports, it has two valid outcomes.
:returns The view function being wrapped with kwarg `comment` set to :returns The view function being wrapped with kwarg `comment` set to
the MediaComment who's id is in the URL. If there is a the Comment who's id is in the URL. If there is a
comment id in the URL and if it is valid. comment id in the URL and if it is valid.
:returns The view function being wrapped with kwarg `comment` set to :returns The view function being wrapped with kwarg `comment` set to
None. If there is no comment id in the URL. None. If there is no comment id in the URL.
@ -339,8 +340,9 @@ def get_optional_media_comment_by_id(controller):
@wraps(controller) @wraps(controller)
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
if 'comment' in request.matchdict: if 'comment' in request.matchdict:
comment = MediaComment.query.filter_by( comment = Comment.query.filter_by(
id=request.matchdict['comment']).first() id=request.matchdict['comment']
).first()
if comment is None: if comment is None:
return render_404(request) return render_404(request)

View File

@ -68,14 +68,14 @@ def take_punitive_actions(request, form, report, user):
if u'delete' in form.action_to_resolve.data and \ if u'delete' in form.action_to_resolve.data and \
report.is_comment_report(): report.is_comment_report():
deleted_comment = report.comment deleted_comment = report.obj()
Session.delete(deleted_comment) Session.delete(deleted_comment)
form.resolution_content.data += \ form.resolution_content.data += \
_(u"\n{mod} deleted the comment.").format( _(u"\n{mod} deleted the comment.").format(
mod=request.user.username) mod=request.user.username)
elif u'delete' in form.action_to_resolve.data and \ elif u'delete' in form.action_to_resolve.data and \
report.is_media_entry_report(): report.is_media_entry_report():
deleted_media = report.media_entry deleted_media = report.obj()
deleted_media.delete() deleted_media.delete()
form.resolution_content.data += \ form.resolution_content.data += \
_(u"\n{mod} deleted the media entry.").format( _(u"\n{mod} deleted the media entry.").format(

View File

@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from mediagoblin.db.models import (MediaEntry, User, ReportBase, Privilege, from mediagoblin.db.models import (MediaEntry, User, Report, Privilege,
UserBan, LocalUser) UserBan, LocalUser)
from mediagoblin.decorators import (require_admin_or_moderator_login, from mediagoblin.decorators import (require_admin_or_moderator_login,
active_user_from_url, user_has_privilege, active_user_from_url, user_has_privilege,
@ -83,9 +83,9 @@ def moderation_users_detail(request):
LocalUser.username==request.matchdict['user'] LocalUser.username==request.matchdict['user']
).first() ).first()
active_reports = user.reports_filed_on.filter( active_reports = user.reports_filed_on.filter(
ReportBase.resolved==None).limit(5) Report.resolved==None).limit(5)
closed_reports = user.reports_filed_on.filter( closed_reports = user.reports_filed_on.filter(
ReportBase.resolved!=None).all() Report.resolved!=None).all()
privileges = Privilege.query privileges = Privilege.query
user_banned = UserBan.query.get(user.id) user_banned = UserBan.query.get(user.id)
ban_form = moderation_forms.BanForm() ban_form = moderation_forms.BanForm()
@ -116,23 +116,23 @@ def moderation_reports_panel(request):
active_settings['current_page'] = form.active_p.data or 1 active_settings['current_page'] = form.active_p.data or 1
closed_settings['current_page'] = form.closed_p.data or 1 closed_settings['current_page'] = form.closed_p.data or 1
filters = [ filters = [
getattr(ReportBase,key)==val getattr(Report,key)==val
for key,val in filters.viewitems()] for key,val in filters.viewitems()]
all_active = ReportBase.query.filter( all_active = Report.query.filter(
ReportBase.resolved==None).filter( Report.resolved==None).filter(
*filters) *filters)
all_closed = ReportBase.query.filter( all_closed = Report.query.filter(
ReportBase.resolved!=None).filter( Report.resolved!=None).filter(
*filters) *filters)
# report_list and closed_report_list are the two lists of up to 10 # report_list and closed_report_list are the two lists of up to 10
# items which are actually passed to the user in this request # items which are actually passed to the user in this request
report_list = all_active.order_by( report_list = all_active.order_by(
ReportBase.created.desc()).offset( Report.created.desc()).offset(
(active_settings['current_page']-1)*10).limit(10) (active_settings['current_page']-1)*10).limit(10)
closed_report_list = all_closed.order_by( closed_report_list = all_closed.order_by(
ReportBase.created.desc()).offset( Report.created.desc()).offset(
(closed_settings['current_page']-1)*10).limit(10) (closed_settings['current_page']-1)*10).limit(10)
active_settings['last_page'] = int(ceil(all_active.count()/10.)) active_settings['last_page'] = int(ceil(all_active.count()/10.))
@ -155,7 +155,7 @@ def moderation_reports_detail(request):
erator would go to to take an action to resolve a report. erator would go to to take an action to resolve a report.
""" """
form = moderation_forms.ReportResolutionForm(request.form) form = moderation_forms.ReportResolutionForm(request.form)
report = ReportBase.query.get(request.matchdict['report_id']) report = Report.query.get(request.matchdict['report_id'])
form.take_away_privileges.choices = [ form.take_away_privileges.choices = [
(s.privilege_name,s.privilege_name.title()) \ (s.privilege_name,s.privilege_name.title()) \

View File

@ -16,8 +16,8 @@
import logging import logging
from mediagoblin.db.models import Notification, \ from mediagoblin.db.models import Notification, CommentSubscription, User, \
CommentNotification, CommentSubscription, User Comment, GenericModelReference
from mediagoblin.notifications.task import email_notification_task from mediagoblin.notifications.task import email_notification_task
from mediagoblin.notifications.tools import generate_comment_message from mediagoblin.notifications.tools import generate_comment_message
@ -37,10 +37,10 @@ def trigger_notification(comment, media_entry, request):
if comment.get_actor == subscription.user: if comment.get_actor == subscription.user:
continue continue
cn = CommentNotification( cn = Notification(
user_id=subscription.user_id, user_id=subscription.user_id,
subject_id=comment.id) )
cn.obj = comment
cn.save() cn.save()
if subscription.send_email: if subscription.send_email:
@ -61,9 +61,15 @@ def mark_notification_seen(notification):
def mark_comment_notification_seen(comment_id, user): def mark_comment_notification_seen(comment_id, user):
notification = CommentNotification.query.filter_by( comment = Comment.query.get(comment_id).comment()
comment_gmr = GenericModelReference.query.filter_by(
obj_pk=comment.id,
model_type=comment.__tablename__
).first()
notification = Notification.query.filter_by(
user_id=user.id, user_id=user.id,
subject_id=comment_id).first() object_id=comment_gmr.id
).first()
_log.debug(u'Marking {0} as seen.'.format(notification)) _log.debug(u'Marking {0} as seen.'.format(notification))

View File

@ -20,7 +20,7 @@ from celery import registry
from celery.task import Task from celery.task import Task
from mediagoblin.tools.mail import send_email from mediagoblin.tools.mail import send_email
from mediagoblin.db.models import CommentNotification from mediagoblin.db.models import Notification
_log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
@ -34,7 +34,7 @@ class EmailNotificationTask(Task):
the web server. the web server.
''' '''
def run(self, notification_id, message): def run(self, notification_id, message):
cn = CommentNotification.query.filter_by(id=notification_id).first() cn = Notification.query.filter_by(id=notification_id).first()
_log.info(u'Sending notification email about {0}'.format(cn)) _log.info(u'Sending notification email about {0}'.format(cn))
return send_email( return send_email(

View File

@ -57,7 +57,10 @@ def mark_all_comment_notifications_seen(request):
Marks all comment notifications seen. Marks all comment notifications seen.
""" """
for comment in get_notifications(request.user.id): for comment in get_notifications(request.user.id):
mark_comment_notification_seen(comment.subject_id, request.user) mark_comment_notification_seen(
comment.obj().get_comment_link().id,
request.user
)
if request.GET.get('next'): if request.GET.get('next'):
return redirect(request, location=request.GET.get('next')) return redirect(request, location=request.GET.get('next'))

View File

@ -281,6 +281,9 @@ def api_upload_request(request, file_data, entry):
# This will be set later but currently we just don't have enough information # This will be set later but currently we just don't have enough information
entry.slug = None entry.slug = None
# This is a MUST.
entry.get_public_id(request.urlgen)
queue_file = prepare_queue_task(request.app, entry, file_data.filename) queue_file = prepare_queue_task(request.app, entry, file_data.filename)
with queue_file: with queue_file:
queue_file.write(request.data) queue_file.write(request.data)

View File

@ -4,9 +4,9 @@
<h3>{% trans %}New comments{% endtrans %}</h3> <h3>{% trans %}New comments{% endtrans %}</h3>
<ul> <ul>
{% for notification in notifications %} {% for notification in notifications %}
{% set comment = notification.subject %} {% set comment = notification.obj() %}
{% set comment_author = comment.get_actor %} {% set comment_author = comment.get_actor %}
{% set media = comment.get_entry %} {% set media = comment.get_reply_to() %}
<li class="comment_wrapper"> <li class="comment_wrapper">
<div class="comment_author"> <div class="comment_author">
<img src="{{ request.staticdirect('/images/icon_comment.png') }}" /> <img src="{{ request.staticdirect('/images/icon_comment.png') }}" />

View File

@ -33,10 +33,11 @@
{% trans %}Return to Reports Panel{% endtrans %}</a> {% trans %}Return to Reports Panel{% endtrans %}</a>
</div> </div>
<h2>{% trans %}Report{% endtrans %} #{{ report.id }}</h2> <h2>{% trans %}Report{% endtrans %} #{{ report.id }}</h2>
{% if report.is_comment_report() and report.comment %} {% if report.is_comment_report() and report.object_id %}
{% trans %}Reported comment{% endtrans %}: {% trans %}Reported comment{% endtrans %}:
{% set comment = report.comment %} {% set comment = report.obj() %}
{% set target = report.obj().get_reply_to() %}
{% set reported_user = comment.get_actor %} {% set reported_user = comment.get_actor %}
<div id="comment-{{ comment.id }}" <div id="comment-{{ comment.id }}"
class="comment_wrapper"> class="comment_wrapper">
@ -50,8 +51,8 @@
<a href="{{ request.urlgen( <a href="{{ request.urlgen(
'mediagoblin.user_pages.media_home.view_comment', 'mediagoblin.user_pages.media_home.view_comment',
comment=comment.id, comment=comment.id,
user=comment.get_media_entry.get_actor.username, user=target.get_actor.username,
media=comment.get_media_entry.slug_or_id) }}#comment" media=target.slug_or_id) }}#comment"
class="comment_whenlink"> class="comment_whenlink">
<span title='{{- comment.created.strftime("%I:%M%p %Y-%m-%d") -}}'> <span title='{{- comment.created.strftime("%I:%M%p %Y-%m-%d") -}}'>
{%- trans formatted_time=timesince(comment.created) -%} {%- trans formatted_time=timesince(comment.created) -%}
@ -65,9 +66,9 @@
{% endautoescape %} {% endautoescape %}
</div> </div>
</div> </div>
{% elif report.is_media_entry_report() and report.media_entry %} {% elif report.is_media_entry_report() and report.object_id %}
{% set media_entry = report.media_entry %} {% set media_entry = report.obj() %}
<div class="three columns media_thumbnail"> <div class="three columns media_thumbnail">
<a href="{{ request.urlgen('mediagoblin.user_pages.media_home', <a href="{{ request.urlgen('mediagoblin.user_pages.media_home',
user=media_entry.get_actor.username, user=media_entry.get_actor.username,

View File

@ -81,7 +81,7 @@ curr_page !=p %}
</tr> </tr>
{% for report in report_list %} {% for report in report_list %}
<tr> <tr>
{% if report.discriminator == "comment_report" %} {% if report.is_comment_report %}
<td> <td>
<img <img
src="{{ request.staticdirect( src="{{ request.staticdirect(
@ -97,7 +97,7 @@ curr_page !=p %}
{% endtrans %} {% endtrans %}
</a> </a>
</td> </td>
{% elif report.discriminator == "media_report" %} {% elif report.is_media_entry_report %}
<td> <td>
<img <img
src="{{ request.staticdirect( src="{{ request.staticdirect(

View File

@ -125,9 +125,9 @@
</a> </a>
</td> </td>
<td> <td>
{% if report.discriminator == "comment_report" %} {% if report.is_comment_report() %}
<a>{%- trans %}Reported Comment{% endtrans -%}</a> <a>{%- trans %}Reported Comment{% endtrans -%}</a>
{% elif report.discriminator == "media_report" %} {% elif report.is_media_entry_report() %}
<a>{%- trans %}Reported Media Entry{% endtrans -%}</a> <a>{%- trans %}Reported Media Entry{% endtrans -%}</a>
{% endif %} {% endif %}
</td> </td>

View File

@ -93,7 +93,7 @@
<p>{{ media.description_html }}</p> <p>{{ media.description_html }}</p>
{% endautoescape %} {% endautoescape %}
</div> </div>
{% if comments and request.user and request.user.has_privilege('commenter') %} {% if request.user and request.user.has_privilege('commenter') %}
<div class="media_comments"> <div class="media_comments">
{% if app_config['allow_comments'] %} {% if app_config['allow_comments'] %}
<a <a

View File

@ -25,7 +25,7 @@ from webtest import AppError
from .resources import GOOD_JPG from .resources import GOOD_JPG
from mediagoblin import mg_globals from mediagoblin import mg_globals
from mediagoblin.db.models import User, MediaEntry, MediaComment from mediagoblin.db.models import User, MediaEntry, TextComment
from mediagoblin.tools.routing import extract_url_arguments from mediagoblin.tools.routing import extract_url_arguments
from mediagoblin.tests.tools import fixture_add_user from mediagoblin.tests.tools import fixture_add_user
from mediagoblin.moderation.tools import take_away_privileges from mediagoblin.moderation.tools import take_away_privileges
@ -188,8 +188,7 @@ class TestAPI(object):
# Lets change the image uploader to be self.other_user, this is easier # Lets change the image uploader to be self.other_user, this is easier
# than uploading the image as someone else as the way self.mocked_oauth_required # than uploading the image as someone else as the way self.mocked_oauth_required
# and self._upload_image. # and self._upload_image.
id = int(data["object"]["id"].split("/")[-2]) media = MediaEntry.query.filter_by(public_id=data["object"]["id"]).first()
media = MediaEntry.query.filter_by(id=id).first()
media.actor = self.other_user.id media.actor = self.other_user.id
media.save() media.save()
@ -232,14 +231,13 @@ class TestAPI(object):
image = json.loads(response.body.decode())["object"] image = json.loads(response.body.decode())["object"]
# Check everything has been set on the media correctly # Check everything has been set on the media correctly
id = int(image["id"].split("/")[-2]) media = MediaEntry.query.filter_by(public_id=image["id"]).first()
media = MediaEntry.query.filter_by(id=id).first()
assert media.title == title assert media.title == title
assert media.description == description assert media.description == description
assert media.license == license assert media.license == license
# Check we're being given back everything we should on an update # Check we're being given back everything we should on an update
assert int(image["id"].split("/")[-2]) == media.id assert image["id"] == media.public_id
assert image["displayName"] == title assert image["displayName"] == title
assert image["content"] == description assert image["content"] == description
assert image["license"] == license assert image["license"] == license
@ -288,8 +286,7 @@ class TestAPI(object):
request = test_app.get(object_uri) request = test_app.get(object_uri)
image = json.loads(request.body.decode()) image = json.loads(request.body.decode())
entry_id = int(image["id"].split("/")[-2]) entry = MediaEntry.query.filter_by(public_id=image["id"]).first()
entry = MediaEntry.query.filter_by(id=entry_id).first()
assert request.status_code == 200 assert request.status_code == 200
@ -319,8 +316,7 @@ class TestAPI(object):
assert response.status_code == 200 assert response.status_code == 200
# Find the objects in the database # Find the objects in the database
media_id = int(data["object"]["id"].split("/")[-2]) media = MediaEntry.query.filter_by(public_id=data["object"]["id"]).first()
media = MediaEntry.query.filter_by(id=media_id).first()
comment = media.get_comments()[0] comment = media.get_comments()[0]
# Tests that it matches in the database # Tests that it matches in the database
@ -382,8 +378,7 @@ class TestAPI(object):
response, comment_data = self._activity_to_feed(test_app, activity) response, comment_data = self._activity_to_feed(test_app, activity)
# change who uploaded the comment as it's easier than changing # change who uploaded the comment as it's easier than changing
comment_id = int(comment_data["object"]["id"].split("/")[-2]) comment = TextComment.query.filter_by(public_id=comment_data["object"]["id"]).first()
comment = MediaComment.query.filter_by(id=comment_id).first()
comment.actor = self.other_user.id comment.actor = self.other_user.id
comment.save() comment.save()
@ -510,8 +505,7 @@ class TestAPI(object):
response = self._activity_to_feed(test_app, activity)[1] response = self._activity_to_feed(test_app, activity)[1]
# Check the media is no longer in the database # Check the media is no longer in the database
media_id = int(object_id.split("/")[-2]) media = MediaEntry.query.filter_by(public_id=object_id).first()
media = MediaEntry.query.filter_by(id=media_id).first()
assert media is None assert media is None
@ -552,8 +546,8 @@ class TestAPI(object):
delete = self._activity_to_feed(test_app, activity)[1] delete = self._activity_to_feed(test_app, activity)[1]
# Verify the comment no longer exists # Verify the comment no longer exists
comment_id = int(comment["object"]["id"].split("/")[-2]) assert TextComment.query.filter_by(public_id=comment["object"]["id"]).first() is None
assert MediaComment.query.filter_by(id=comment_id).first() is None comment_id = comment["object"]["id"]
# Check we've got a delete activity back # Check we've got a delete activity back
assert "id" in delete assert "id" in delete
@ -593,7 +587,6 @@ class TestAPI(object):
comment = self._activity_to_feed(test_app, activity)[1] comment = self._activity_to_feed(test_app, activity)[1]
# Verify the comment reflects the changes # Verify the comment reflects the changes
comment_id = int(comment["object"]["id"].split("/")[-2]) model = TextComment.query.filter_by(public_id=comment["object"]["id"]).first()
model = MediaComment.query.filter_by(id=comment_id).first()
assert model.content == activity["object"]["content"] assert model.content == activity["object"]["content"]

View File

@ -24,7 +24,7 @@ from mediagoblin.db.base import Session
from mediagoblin.media_types import sniff_media from mediagoblin.media_types import sniff_media
from mediagoblin.submit.lib import new_upload_entry from mediagoblin.submit.lib import new_upload_entry
from mediagoblin.submit.task import collect_garbage from mediagoblin.submit.task import collect_garbage
from mediagoblin.db.models import User, MediaEntry, MediaComment from mediagoblin.db.models import User, MediaEntry, TextComment, Comment
from mediagoblin.tests.tools import fixture_add_user, fixture_media_entry from mediagoblin.tests.tools import fixture_add_user, fixture_media_entry
@ -46,25 +46,31 @@ def test_user_deletes_other_comments(test_app):
Session.flush() Session.flush()
# Create all 4 possible comments: # Create all 4 possible comments:
for u_id in (user_a.id, user_b.id): for u in (user_a, user_b):
for m_id in (media_a.id, media_b.id): for m in (media_a, media_b):
cmt = MediaComment() cmt = TextComment()
cmt.media_entry = m_id cmt.actor = u.id
cmt.actor = u_id
cmt.content = u"Some Comment" cmt.content = u"Some Comment"
Session.add(cmt) Session.add(cmt)
# think i need this to get the command ID
Session.flush()
link = Comment()
link.target = m
link.comment = cmt
Session.add(link)
Session.flush() Session.flush()
usr_cnt1 = User.query.count() usr_cnt1 = User.query.count()
med_cnt1 = MediaEntry.query.count() med_cnt1 = MediaEntry.query.count()
cmt_cnt1 = MediaComment.query.count() cmt_cnt1 = TextComment.query.count()
User.query.get(user_a.id).delete(commit=False) User.query.get(user_a.id).delete(commit=False)
usr_cnt2 = User.query.count() usr_cnt2 = User.query.count()
med_cnt2 = MediaEntry.query.count() med_cnt2 = MediaEntry.query.count()
cmt_cnt2 = MediaComment.query.count() cmt_cnt2 = TextComment.query.count()
# One user deleted # One user deleted
assert usr_cnt2 == usr_cnt1 - 1 assert usr_cnt2 == usr_cnt1 - 1
@ -77,7 +83,7 @@ def test_user_deletes_other_comments(test_app):
usr_cnt2 = User.query.count() usr_cnt2 = User.query.count()
med_cnt2 = MediaEntry.query.count() med_cnt2 = MediaEntry.query.count()
cmt_cnt2 = MediaComment.query.count() cmt_cnt2 = TextComment.query.count()
# All users gone # All users gone
assert usr_cnt2 == usr_cnt1 - 2 assert usr_cnt2 == usr_cnt1 - 2

View File

@ -18,7 +18,8 @@ import pytest
from mediagoblin.tests.tools import (fixture_add_user, from mediagoblin.tests.tools import (fixture_add_user,
fixture_add_comment_report, fixture_add_comment) fixture_add_comment_report, fixture_add_comment)
from mediagoblin.db.models import User, LocalUser, CommentReport, MediaComment, UserBan from mediagoblin.db.models import User, LocalUser, Report, TextComment, \
UserBan, GenericModelReference
from mediagoblin.tools import template, mail from mediagoblin.tools import template, mail
from webtest import AppError from webtest import AppError
@ -102,15 +103,15 @@ class TestModerationViews:
# to a reported comment # to a reported comment
#---------------------------------------------------------------------- #----------------------------------------------------------------------
fixture_add_comment_report(reported_user=self.user) fixture_add_comment_report(reported_user=self.user)
comment_report = CommentReport.query.filter( comment_report = Report.query.filter(
CommentReport.reported_user==self.user).first() Report.reported_user==self.user).first()
response = self.test_app.get('/mod/reports/{0}/'.format( response = self.test_app.get('/mod/reports/{0}/'.format(
comment_report.id)) comment_report.id))
assert response.status == '200 OK' assert response.status == '200 OK'
self.query_for_users() self.query_for_users()
comment_report = CommentReport.query.filter( comment_report = Report.query.filter(
CommentReport.reported_user==self.user).first() Report.reported_user==self.user).first()
response, context = self.do_post({'action_to_resolve':[u'takeaway'], response, context = self.do_post({'action_to_resolve':[u'takeaway'],
'take_away_privileges':[u'commenter'], 'take_away_privileges':[u'commenter'],
@ -118,15 +119,15 @@ class TestModerationViews:
url='/mod/reports/{0}/'.format(comment_report.id)) url='/mod/reports/{0}/'.format(comment_report.id))
self.query_for_users() self.query_for_users()
comment_report = CommentReport.query.filter( comment_report = Report.query.filter(
CommentReport.reported_user==self.user).first() Report.reported_user==self.user).first()
assert response.status == '302 FOUND' assert response.status == '302 FOUND'
assert not self.user.has_privilege(u'commenter') assert not self.user.has_privilege(u'commenter')
assert comment_report.is_archived_report() is True assert comment_report.is_archived_report() is True
fixture_add_comment_report(reported_user=self.user) fixture_add_comment_report(reported_user=self.user)
comment_report = CommentReport.query.filter( comment_report = Report.query.filter(
CommentReport.reported_user==self.user).first() Report.reported_user==self.user).first()
# Then, test a moderator sending an email to a user in response to a # Then, test a moderator sending an email to a user in response to a
# reported comment # reported comment
@ -139,8 +140,8 @@ class TestModerationViews:
url='/mod/reports/{0}/'.format(comment_report.id)) url='/mod/reports/{0}/'.format(comment_report.id))
self.query_for_users() self.query_for_users()
comment_report = CommentReport.query.filter( comment_report = Report.query.filter(
CommentReport.reported_user==self.user).first() Report.reported_user==self.user).first()
assert response.status == '302 FOUND' assert response.status == '302 FOUND'
assert mail.EMAIL_TEST_MBOX_INBOX == [{'to': [u'regular@example.com'], assert mail.EMAIL_TEST_MBOX_INBOX == [{'to': [u'regular@example.com'],
'message': 'Content-Type: text/plain; charset="utf-8"\n\ 'message': 'Content-Type: text/plain; charset="utf-8"\n\
@ -157,13 +158,17 @@ VGhpcyBpcyB5b3VyIGxhc3Qgd2FybmluZywgcmVndWxhci4uLi4=\n',
self.query_for_users() self.query_for_users()
fixture_add_comment(author=self.user.id, fixture_add_comment(author=self.user.id,
comment=u'Comment will be removed') comment=u'Comment will be removed')
test_comment = MediaComment.query.filter( test_comment = TextComment.query.filter(
MediaComment.actor==self.user.id).first() TextComment.actor==self.user.id).first()
fixture_add_comment_report(comment=test_comment, fixture_add_comment_report(comment=test_comment,
reported_user=self.user) reported_user=self.user)
comment_report = CommentReport.query.filter( comment_gmr = GenericModelReference.query.filter_by(
CommentReport.comment==test_comment).filter( obj_pk=test_comment.id,
CommentReport.resolved==None).first() model_type=test_comment.__tablename__
).first()
comment_report = Report.query.filter(
Report.object_id==comment_gmr.id).filter(
Report.resolved==None).first()
response, context = self.do_post( response, context = self.do_post(
{'action_to_resolve':[u'userban', u'delete'], {'action_to_resolve':[u'userban', u'delete'],
@ -176,17 +181,17 @@ VGhpcyBpcyB5b3VyIGxhc3Qgd2FybmluZywgcmVndWxhci4uLi4=\n',
test_user_ban = UserBan.query.filter( test_user_ban = UserBan.query.filter(
UserBan.user_id == self.user.id).first() UserBan.user_id == self.user.id).first()
assert test_user_ban is not None assert test_user_ban is not None
test_comment = MediaComment.query.filter( test_comment = TextComment.query.filter(
MediaComment.actor==self.user.id).first() TextComment.actor==self.user.id).first()
assert test_comment is None assert test_comment is None
# Then, test what happens when a moderator attempts to punish an admin # Then, test what happens when a moderator attempts to punish an admin
# from a reported comment on an admin. # from a reported comment on an admin.
#---------------------------------------------------------------------- #----------------------------------------------------------------------
fixture_add_comment_report(reported_user=self.admin_user) fixture_add_comment_report(reported_user=self.admin_user)
comment_report = CommentReport.query.filter( comment_report = Report.query.filter(
CommentReport.reported_user==self.admin_user).filter( Report.reported_user==self.admin_user).filter(
CommentReport.resolved==None).first() Report.resolved==None).first()
response, context = self.do_post({'action_to_resolve':[u'takeaway'], response, context = self.do_post({'action_to_resolve':[u'takeaway'],
'take_away_privileges':[u'active'], 'take_away_privileges':[u'active'],

View File

@ -20,8 +20,7 @@ import six.moves.urllib.parse as urlparse
from mediagoblin.tools import template, mail from mediagoblin.tools import template, mail
from mediagoblin.db.models import Notification, CommentNotification, \ from mediagoblin.db.models import Notification, CommentSubscription
CommentSubscription
from mediagoblin.db.base import Session from mediagoblin.db.base import Session
from mediagoblin.notifications import mark_comment_notification_seen from mediagoblin.notifications import mark_comment_notification_seen
@ -109,11 +108,10 @@ class TestNotifications:
notification = notifications[0] notification = notifications[0]
assert type(notification) == CommentNotification
assert notification.seen == False assert notification.seen == False
assert notification.user_id == user.id assert notification.user_id == user.id
assert notification.subject.get_actor.id == self.test_user.id assert notification.obj().get_actor.id == self.test_user.id
assert notification.subject.content == u'Test comment #42' assert notification.obj().content == u'Test comment #42'
if wants_email == True: if wants_email == True:
assert mail.EMAIL_TEST_MBOX_INBOX == [ assert mail.EMAIL_TEST_MBOX_INBOX == [
@ -130,7 +128,7 @@ otherperson@example.com\n\nSGkgb3RoZXJwZXJzb24sCmNocmlzIGNvbW1lbnRlZCBvbiB5b3VyI
# Save the ids temporarily because of DetachedInstanceError # Save the ids temporarily because of DetachedInstanceError
notification_id = notification.id notification_id = notification.id
comment_id = notification.subject.id comment_id = notification.obj().get_comment_link().id
self.logout() self.logout()
self.login('otherperson', 'nosreprehto') self.login('otherperson', 'nosreprehto')

View File

@ -20,8 +20,7 @@ import six
from mediagoblin.tools import template from mediagoblin.tools import template
from mediagoblin.tests.tools import (fixture_add_user, fixture_media_entry, from mediagoblin.tests.tools import (fixture_add_user, fixture_media_entry,
fixture_add_comment, fixture_add_comment_report) fixture_add_comment, fixture_add_comment_report)
from mediagoblin.db.models import (MediaReport, CommentReport, User, LocalUser, from mediagoblin.db.models import Report, User, LocalUser, TextComment
MediaComment)
class TestReportFiling: class TestReportFiling:
@ -80,7 +79,7 @@ class TestReportFiling:
assert response.status == "302 FOUND" assert response.status == "302 FOUND"
media_report = MediaReport.query.first() media_report = Report.query.first()
allie_user, natalie_user = self.query_for_users() allie_user, natalie_user = self.query_for_users()
assert media_report is not None assert media_report is not None
@ -88,7 +87,6 @@ class TestReportFiling:
assert media_report.reporter_id == allie_id assert media_report.reporter_id == allie_id
assert media_report.reported_user_id == natalie_user.id assert media_report.reported_user_id == natalie_user.id
assert media_report.created is not None assert media_report.created is not None
assert media_report.discriminator == 'media_report'
def testCommentReports(self): def testCommentReports(self):
self.login(u'allie') self.login(u'allie')
@ -98,9 +96,11 @@ class TestReportFiling:
media_entry = fixture_media_entry(uploader=natalie_user.id, media_entry = fixture_media_entry(uploader=natalie_user.id,
state=u'processed') state=u'processed')
mid = media_entry.id mid = media_entry.id
fixture_add_comment(media_entry=mid, fixture_add_comment(
author=natalie_user.id) media_entry=media_entry,
comment = MediaComment.query.first() author=natalie_user.id
)
comment = TextComment.query.first()
comment_uri_slug = '/u/{0}/m/{1}/c/{2}/'.format(natalie_user.username, comment_uri_slug = '/u/{0}/m/{1}/c/{2}/'.format(natalie_user.username,
media_entry.slug, media_entry.slug,
@ -115,7 +115,7 @@ class TestReportFiling:
assert response.status == "302 FOUND" assert response.status == "302 FOUND"
comment_report = CommentReport.query.first() comment_report = Report.query.first()
allie_user, natalie_user = self.query_for_users() allie_user, natalie_user = self.query_for_users()
assert comment_report is not None assert comment_report is not None
@ -123,7 +123,6 @@ class TestReportFiling:
assert comment_report.reporter_id == allie_id assert comment_report.reporter_id == allie_id
assert comment_report.reported_user_id == natalie_user.id assert comment_report.reported_user_id == natalie_user.id
assert comment_report.created is not None assert comment_report.created is not None
assert comment_report.discriminator == 'comment_report'
def testArchivingReports(self): def testArchivingReports(self):
self.login(u'natalie') self.login(u'natalie')
@ -132,14 +131,14 @@ class TestReportFiling:
fixture_add_comment(author=allie_user.id, fixture_add_comment(author=allie_user.id,
comment=u'Comment will be removed') comment=u'Comment will be removed')
test_comment = MediaComment.query.filter( test_comment = TextComment.query.filter(
MediaComment.actor==allie_user.id).first() TextComment.actor==allie_user.id).first()
fixture_add_comment_report(comment=test_comment, fixture_add_comment_report(comment=test_comment,
reported_user=allie_user, reported_user=allie_user,
report_content=u'Testing Archived Reports #1', report_content=u'Testing Archived Reports #1',
reporter=natalie_user) reporter=natalie_user)
comment_report = CommentReport.query.filter( comment_report = Report.query.filter(
CommentReport.reported_user==allie_user).first() Report.reported_user==allie_user).first()
assert comment_report.report_content == u'Testing Archived Reports #1' assert comment_report.report_content == u'Testing Archived Reports #1'
response, context = self.do_post( response, context = self.do_post(
@ -151,10 +150,10 @@ class TestReportFiling:
assert response.status == "302 FOUND" assert response.status == "302 FOUND"
allie_user, natalie_user = self.query_for_users() allie_user, natalie_user = self.query_for_users()
archived_report = CommentReport.query.filter( archived_report = Report.query.filter(
CommentReport.reported_user==allie_user).first() Report.reported_user==allie_user).first()
assert CommentReport.query.count() != 0 assert Report.query.count() != 0
assert archived_report is not None assert archived_report is not None
assert archived_report.report_content == u'Testing Archived Reports #1' assert archived_report.report_content == u'Testing Archived Reports #1'
assert archived_report.reporter_id == natalie_id assert archived_report.reporter_id == natalie_id
@ -164,4 +163,3 @@ class TestReportFiling:
assert archived_report.result == u'''This is a test of archiving reports. assert archived_report.result == u'''This is a test of archiving reports.
natalie banned user allie indefinitely. natalie banned user allie indefinitely.
natalie deleted the comment.''' natalie deleted the comment.'''
assert archived_report.discriminator == 'comment_report'

View File

@ -99,8 +99,14 @@ class TestSubmission:
return {'upload_files': [('file', filename)]} return {'upload_files': [('file', filename)]}
def check_comments(self, request, media_id, count): def check_comments(self, request, media_id, count):
comments = request.db.MediaComment.query.filter_by(media_entry=media_id) gmr = request.db.GenericModelReference.query.filter_by(
assert count == len(list(comments)) obj_pk=media_id,
model_type=request.db.MediaEntry.__tablename__
).first()
if gmr is None and count <= 0:
return # Yerp it's fine.
comments = request.db.Comment.query.filter_by(target_id=gmr.id)
assert count == comments.count()
def test_missing_fields(self): def test_missing_fields(self):
# Test blank form # Test blank form

View File

@ -25,9 +25,9 @@ from paste.deploy import loadapp
from webtest import TestApp from webtest import TestApp
from mediagoblin import mg_globals from mediagoblin import mg_globals
from mediagoblin.db.models import User, LocalUser, MediaEntry, Collection, MediaComment, \ from mediagoblin.db.models import User, LocalUser, MediaEntry, Collection, TextComment, \
CommentSubscription, CommentNotification, Privilege, CommentReport, Client, \ CommentSubscription, Notification, Privilege, Report, Client, \
RequestToken, AccessToken, Activity, Generator RequestToken, AccessToken, Activity, Generator, Comment
from mediagoblin.tools import testing from mediagoblin.tools import testing
from mediagoblin.init.config import read_mediagoblin_config from mediagoblin.init.config import read_mediagoblin_config
from mediagoblin.db.base import Session from mediagoblin.db.base import Session
@ -222,14 +222,16 @@ def fixture_comment_subscription(entry, notify=True, send_email=None):
return cs return cs
def fixture_add_comment_notification(entry_id, subject_id, user_id, def fixture_add_comment_notification(entry, subject, user,
seen=False): seen=False):
cn = CommentNotification(user_id=user_id, cn = Notification(
seen=seen, user_id=user,
subject_id=subject_id) seen=seen,
)
cn.obj = subject
cn.save() cn.save()
cn = CommentNotification.query.filter_by(id=cn.id).first() cn = Notification.query.filter_by(id=cn.id).first()
Session.expunge(cn) Session.expunge(cn)
@ -309,22 +311,27 @@ def fixture_add_comment(author=None, media_entry=None, comment=None):
author = fixture_add_user().id author = fixture_add_user().id
if media_entry is None: if media_entry is None:
media_entry = fixture_media_entry().id media_entry = fixture_media_entry()
if comment is None: if comment is None:
comment = \ comment = \
'Auto-generated test comment by user #{0} on media #{0}'.format( 'Auto-generated test comment by user #{0} on media #{0}'.format(
author, media_entry) author, media_entry)
comment = MediaComment(actor=author, text_comment = TextComment(
media_entry=media_entry, actor=author,
content=comment) content=comment
)
text_comment.save()
comment.save() comment_link = Comment()
comment_link.target = media_entry
comment_link.comment = text_comment
comment_link.save()
Session.expunge(comment) Session.expunge(comment_link)
return comment return text_comment
def fixture_add_comment_report(comment=None, reported_user=None, def fixture_add_comment_report(comment=None, reported_user=None,
reporter=None, created=None, report_content=None): reporter=None, created=None, report_content=None):
@ -344,12 +351,13 @@ def fixture_add_comment_report(comment=None, reported_user=None,
report_content = \ report_content = \
'Auto-generated test report' 'Auto-generated test report'
comment_report = CommentReport(comment=comment, comment_report = Report()
reported_user = reported_user, comment_report.obj = comment
reporter = reporter, comment_report.reported_user = reported_user
created = created, comment_report.reporter = reporter
report_content=report_content) comment_report.created = created
comment_report.report_content = report_content
comment_report.obj = comment
comment_report.save() comment_report.save()
Session.expunge(comment_report) Session.expunge(comment_report)

View File

@ -16,8 +16,8 @@
from mediagoblin import mg_globals from mediagoblin import mg_globals
from mediagoblin.db.base import Session from mediagoblin.db.base import Session
from mediagoblin.db.models import (CollectionItem, MediaReport, CommentReport, from mediagoblin.db.models import CollectionItem, Report, TextComment, \
MediaComment, MediaEntry) MediaEntry
from mediagoblin.tools.mail import send_email from mediagoblin.tools.mail import send_email
from mediagoblin.tools.pluginapi import hook_runall from mediagoblin.tools.pluginapi import hook_runall
from mediagoblin.tools.template import render_template from mediagoblin.tools.template import render_template
@ -82,34 +82,27 @@ def add_media_to_collection(collection, media, note=None, commit=True):
def build_report_object(report_form, media_entry=None, comment=None): def build_report_object(report_form, media_entry=None, comment=None):
""" """
This function is used to convert a form object (from a User filing a This function is used to convert a form object (from a User filing a
report) into either a MediaReport or CommentReport object. report) into a Report.
:param report_form A MediaReportForm or a CommentReportForm object :param report_form A MediaReportForm or a CommentReportForm object
with valid information from a POST request. with valid information from a POST request.
:param media_entry A MediaEntry object. The MediaEntry being repo- :param media_entry A MediaEntry object. The MediaEntry being repo-
-rted by a MediaReport. In a CommentReport, -rted by a Report.
this will be None. :param comment A Comment object. The Comment being
:param comment A MediaComment object. The MediaComment being reported by a Report.
reported by a CommentReport. In a MediaReport
this will be None.
:returns A MediaReport object if a valid MediaReportForm is :returns A Report object if a valid MediaReportForm is
passed as kwarg media_entry. This MediaReport has passed as kwarg media_entry. This Report has
not been saved. not been saved.
:returns A CommentReport object if a valid CommentReportForm
is passed as kwarg comment. This CommentReport
has not been saved.
:returns None if the form_dict is invalid. :returns None if the form_dict is invalid.
""" """
report_object = Report()
if report_form.validate() and comment is not None: if report_form.validate() and comment is not None:
report_object = CommentReport() report_object.obj = comment.comment()
report_object.comment_id = comment.id report_object.reported_user_id = TextComment.query.get(
report_object.reported_user_id = MediaComment.query.get(
comment.id).get_actor.id comment.id).get_actor.id
elif report_form.validate() and media_entry is not None: elif report_form.validate() and media_entry is not None:
report_object = MediaReport() report_object.obj = media_entry
report_object.media_entry_id = media_entry.id
report_object.reported_user_id = MediaEntry.query.get( report_object.reported_user_id = MediaEntry.query.get(
media_entry.id).get_actor.id media_entry.id).get_actor.id
else: else:

View File

@ -21,8 +21,9 @@ import json
import six import six
from mediagoblin import messages, mg_globals from mediagoblin import messages, mg_globals
from mediagoblin.db.models import (MediaEntry, MediaTag, Collection, from mediagoblin.db.models import (MediaEntry, MediaTag, Collection, Comment,
CollectionItem, LocalUser, Activity) CollectionItem, LocalUser, Activity, \
GenericModelReference)
from mediagoblin.tools.response import render_to_response, render_404, \ from mediagoblin.tools.response import render_to_response, render_404, \
redirect, redirect_obj redirect, redirect_obj
from mediagoblin.tools.text import cleaned_markdown_conversion from mediagoblin.tools.text import cleaned_markdown_conversion
@ -178,8 +179,7 @@ def media_post_comment(request, media):
if not request.method == 'POST': if not request.method == 'POST':
raise MethodNotAllowed() raise MethodNotAllowed()
comment = request.db.MediaComment() comment = request.db.TextComment()
comment.media_entry = media.id
comment.actor = request.user.id comment.actor = request.user.id
comment.content = six.text_type(request.form['comment_content']) comment.content = six.text_type(request.form['comment_content'])
@ -199,6 +199,11 @@ def media_post_comment(request, media):
add_comment_subscription(request.user, media) add_comment_subscription(request.user, media)
comment.save() comment.save()
link = request.db.Comment()
link.target = media
link.comment = comment
link.save()
messages.add_message( messages.add_message(
request, messages.SUCCESS, request, messages.SUCCESS,
_('Your comment has been posted!')) _('Your comment has been posted!'))
@ -682,15 +687,15 @@ def processing_panel(request):
@get_optional_media_comment_by_id @get_optional_media_comment_by_id
def file_a_report(request, media, comment): def file_a_report(request, media, comment):
""" """
This view handles the filing of a MediaReport or a CommentReport. This view handles the filing of a Report.
""" """
if comment is not None: if comment is not None:
if not comment.get_media_entry.id == media.id: if not comment.target().id == media.id:
return render_404(request) return render_404(request)
form = user_forms.CommentReportForm(request.form) form = user_forms.CommentReportForm(request.form)
context = {'media': media, context = {'media': comment.target(),
'comment':comment, 'comment':comment.comment(),
'form':form} 'form':form}
else: else:
form = user_forms.MediaReportForm(request.form) form = user_forms.MediaReportForm(request.form)
@ -700,9 +705,11 @@ def file_a_report(request, media, comment):
if request.method == "POST": if request.method == "POST":
report_object = build_report_object(form, report_object = build_report_object(
form,
media_entry=media, media_entry=media,
comment=comment) comment=comment
)
# if the object was built successfully, report_table will not be None # if the object was built successfully, report_table will not be None
if report_object: if report_object: