def manage_thumbnail_get_(request): form = request.web_input(submitid="", charid="", auto="") submitid = define.get_int(form.submitid) charid = define.get_int(form.charid) if submitid and request.userid not in staff.ADMINS and request.userid != define.get_ownerid(submitid=submitid): return Response(define.errorpage(request.userid, errorcode.permissions)) elif charid and request.userid not in staff.ADMINS and request.userid != define.get_ownerid(charid=charid): return Response(define.errorpage(request.userid, errorcode.permissions)) elif not submitid and not charid: return Response(define.errorpage(request.userid)) if charid: source_path = define.url_make(charid, "char/.thumb", root=True) if os.path.exists(source_path): source = define.url_make(charid, "char/.thumb") else: source = define.url_make(charid, "char/cover") else: try: source = thumbnail.thumbnail_source(submitid)['display_url'] except WeasylError: source = None return Response(define.webpage(request.userid, "manage/thumbnail.html", [ # Feature "submit" if submitid else "char", # Targetid define.get_targetid(submitid, charid), # Thumbnail source, # Exists bool(source), ], options=['imageselect'], title="Select Thumbnail"))
def select(userid, submitid=None, charid=None, journalid=None): result = [] if submitid: statement = [""" SELECT cm.commentid, cm.parentid, cm.userid, pr.username, lo.settings, cm.content, cm.unixtime, cm.settings, cm.indent, pr.config, cm.hidden_by FROM comments cm INNER JOIN profile pr USING (userid) INNER JOIN login lo USING (userid) WHERE cm.target_sub = %d """ % (submitid,)] else: statement = [""" SELECT cm.commentid, cm.parentid, cm.userid, pr.username, lo.settings, cm.content, cm.unixtime, cm.settings, cm.indent, pr.config, cm.hidden_by FROM %scomment cm INNER JOIN profile pr USING (userid) INNER JOIN login lo USING (userid) WHERE cm.targetid = %i """ % ("submit" if submitid else "char" if charid else "journal", d.get_targetid(submitid, charid, journalid))] # moderators get to view hidden comments if userid not in staff.MODS: statement.append(" AND cm.settings !~ 'h'") if userid: statement.append(m.MACRO_IGNOREUSER % (userid, "cm")) statement.append(" ORDER BY COALESCE(cm.parentid, 0), cm.unixtime") query = d.execute("".join(statement)) for i, comment in enumerate(query): if comment[1]: break result.append({ "commentid": comment[0], "parentid": comment[1], "userid": comment[2], "username": comment[3], "status": "".join({"b", "s"} & set(comment[4])), "content": comment[5], "unixtime": comment[6], "settings": comment[7], "indent": comment[8], "hidden": 'h' in comment[7], "hidden_by": comment[10], }) _thread(query, result, i) media.populate_with_user_media(result) return result
def select(userid, submitid=None, charid=None, journalid=None, updateid=None): is_hidden = "cm.settings ~ 'h'" if submitid: statement = [ """ SELECT cm.commentid, cm.parentid, cm.userid, pr.username, cm.content, cm.unixtime, cm.settings ~ 'h', cm.hidden_by FROM comments cm INNER JOIN profile pr USING (userid) WHERE cm.target_sub = %d """ % (submitid, ) ] else: unixtime = "cm.unixtime" if charid: table = "charcomment" elif journalid: table = "journalcomment" elif updateid: table = "siteupdatecomment" is_hidden = "cm.hidden_at IS NOT NULL" unixtime = "EXTRACT(EPOCH FROM cm.created_at)::int8 + %i" % ( UNIXTIME_OFFSET, ) else: raise WeasylError("Unexpected") statement = [ """ SELECT cm.commentid, cm.parentid, cm.userid, pr.username, cm.content, %s, %s, cm.hidden_by FROM %s cm INNER JOIN profile pr USING (userid) WHERE cm.targetid = %i """ % (unixtime, is_hidden, table, d.get_targetid(submitid, charid, journalid, updateid)) ] # moderators get to view hidden comments if userid not in staff.MODS: statement.append(" AND NOT (%s)" % (is_hidden, )) if userid: statement.append(m.MACRO_IGNOREUSER % (userid, "cm")) statement.append(" ORDER BY cm.commentid") query = d.execute("".join(statement)) result = thread(query, reverse_top_level=False) media.populate_with_user_media(result) return result
def remove(userid, submitid=None, charid=None, journalid=None): d.execute( "DELETE FROM favorite WHERE (userid, targetid, type) = (%i, %i, '%s')", [ userid, d.get_targetid(submitid, charid, journalid), "s" if submitid else "f" if charid else "j" ]) welcome.favorite_remove(userid, submitid=submitid, charid=charid, journalid=journalid)
def check(userid, submitid=None, charid=None, journalid=None): if not userid: return False return d.execute( """ SELECT EXISTS ( SELECT 0 FROM favorite WHERE (userid, targetid, type) = (%i, %i, '%s') ) """, [ userid, d.get_targetid(submitid, charid, journalid), "s" if submitid else "f" if charid else "j" ], options="bool")
def insert(userid, submitid=None, charid=None, journalid=None): if submitid: content_table, id_field, target = "submission", "submitid", submitid elif charid: content_table, id_field, target = "character", "charid", charid else: content_table, id_field, target = "journal", "journalid", journalid query = d.execute("SELECT userid, settings FROM %s WHERE %s = %i", [content_table, id_field, target], options="single") if not query: raise WeasylError("TargetRecordMissing") elif userid == query[0]: raise WeasylError("CannotSelfFavorite") elif "f" in query[1] and not frienduser.check(userid, query[0]): raise WeasylError("FriendsOnly") elif ignoreuser.check(userid, query[0]): raise WeasylError("YouIgnored") elif ignoreuser.check(query[0], userid): raise WeasylError("contentOwnerIgnoredYou") insert_result = d.engine.execute( 'INSERT INTO favorite (userid, targetid, type, unixtime) ' 'VALUES (%(user)s, %(target)s, %(type)s, %(now)s) ' 'ON CONFLICT DO NOTHING', user=userid, target=d.get_targetid(submitid, charid, journalid), type='s' if submitid else 'f' if charid else 'j', now=d.get_time()) if insert_result.rowcount == 0: return # create a list of users to notify notified = set(collection.find_owners(submitid)) # conditions under which "other" should be notified def can_notify(other): other_jsonb = d.get_profile_settings(other) allow_notify = other_jsonb.allow_collection_notifs not_ignored = not ignoreuser.check(other, userid) return allow_notify and not_ignored notified = set(filter(can_notify, notified)) # always notify for own content notified.add(query[0]) for other in notified: welcome.favorite_insert(userid, submitid=submitid, charid=charid, journalid=journalid, otherid=other)
def select(userid, submitid=None, charid=None, journalid=None, updateid=None): is_hidden = "cm.settings ~ 'h'" if submitid: statement = [""" SELECT cm.commentid, cm.parentid, cm.userid, pr.username, cm.content, cm.unixtime, cm.settings ~ 'h', cm.hidden_by FROM comments cm INNER JOIN profile pr USING (userid) WHERE cm.target_sub = %d """ % (submitid,)] else: unixtime = "cm.unixtime" if submitid: table = "submitcomment" elif charid: table = "charcomment" elif journalid: table = "journalcomment" elif updateid: table = "siteupdatecomment" is_hidden = "cm.hidden_at IS NOT NULL" unixtime = "EXTRACT(EPOCH FROM cm.created_at)::int8 + %i" % (UNIXTIME_OFFSET,) else: raise WeasylError("Unexpected") statement = [""" SELECT cm.commentid, cm.parentid, cm.userid, pr.username, cm.content, %s, %s, cm.hidden_by FROM %s cm INNER JOIN profile pr USING (userid) WHERE cm.targetid = %i """ % (unixtime, is_hidden, table, d.get_targetid(submitid, charid, journalid, updateid))] # moderators get to view hidden comments if userid not in staff.MODS: statement.append(" AND NOT (%s)" % (is_hidden,)) if userid: statement.append(m.MACRO_IGNOREUSER % (userid, "cm")) statement.append(" ORDER BY cm.commentid") query = d.execute("".join(statement)) result = thread(query, reverse_top_level=False) media.populate_with_user_media(result) return result
def check(userid, submitid=None, charid=None, journalid=None): if not userid: return False return d.execute(""" SELECT EXISTS ( SELECT 0 FROM favorite WHERE (userid, targetid, type) = (%i, %i, '%s') ) """, [ userid, d.get_targetid(submitid, charid, journalid), "s" if submitid else "f" if charid else "j" ], options="bool")
def check(userid, submitid=None, charid=None, journalid=None): if not userid: return False return d.engine.scalar( """ SELECT EXISTS ( SELECT 0 FROM favorite WHERE (userid, targetid, type) = (%(user)s, %(target)s, %(type)s) ) """, user=userid, target=d.get_targetid(submitid, charid, journalid), type="s" if submitid else "f" if charid else "j", )
def create_favorite(userid, **kwargs): unixtime = kwargs.pop('unixtime', None) favorite.insert(userid, **kwargs) if unixtime is not None: if 'submitid' in kwargs: type_ = 's' elif 'charid' in kwargs: type_ = 'c' elif 'journalid' in kwargs: type_ = 'j' targetid = d.get_targetid(*kwargs.values()) fav = content.Favorite.query.filter_by(userid=userid, type=type_, targetid=targetid).one() fav.unixtime = unixtime content.Favorite.dbsession.flush()
def manage_thumbnail_get_(request): form = request.web_input(submitid="", charid="", auto="") submitid = define.get_int(form.submitid) charid = define.get_int(form.charid) if submitid and request.userid not in staff.ADMINS and request.userid != define.get_ownerid( submitid=submitid): return Response(define.errorpage(request.userid, errorcode.permissions)) elif charid and request.userid not in staff.ADMINS and request.userid != define.get_ownerid( charid=charid): return Response(define.errorpage(request.userid, errorcode.permissions)) elif not submitid and not charid: return Response(define.errorpage(request.userid)) if charid: source_path = define.url_make(charid, "char/.thumb", root=True) if os.path.exists(source_path): source = define.url_make(charid, "char/.thumb") else: source = define.url_make(charid, "char/cover") else: try: source = thumbnail.thumbnail_source(submitid)['display_url'] except WeasylError: source = None return Response( define.webpage( request.userid, "manage/thumbnail.html", [ # Feature "submit" if submitid else "char", # Targetid define.get_targetid(submitid, charid), # Thumbnail source, # Exists bool(source), ], options=['imageselect'], title="Select Thumbnail"))
def insert_transaction(db): insert_result = db.execute( 'INSERT INTO favorite (userid, targetid, type, unixtime) ' 'VALUES (%(user)s, %(target)s, %(type)s, %(now)s) ' 'ON CONFLICT DO NOTHING', user=userid, target=d.get_targetid(submitid, charid, journalid), type='s' if submitid else 'f' if charid else 'j', now=d.get_time()) if insert_result.rowcount == 0: return if submitid: db.execute( "UPDATE submission SET favorites = favorites + 1" " WHERE submitid = %(target)s", target=submitid, ) if not notified: # create a list of users to notify notified_ = collection.find_owners(submitid) # conditions under which "other" should be notified def can_notify(other): other_jsonb = d.get_profile_settings(other) allow_notify = other_jsonb.allow_collection_notifs return allow_notify and not ignoreuser.check(other, userid) notified.extend(u for u in notified_ if can_notify(u)) # always notify for own content notified.append(query[0]) for other in notified: welcome.favorite_insert(db, userid, submitid=submitid, charid=charid, journalid=journalid, otherid=other)
def remove_transaction(db): delete_result = db.execute( "DELETE FROM favorite WHERE (userid, targetid, type) = (%(user)s, %(target)s, %(type)s)", user=userid, target=d.get_targetid(submitid, charid, journalid), type="s" if submitid else "f" if charid else "j", ) if delete_result.rowcount == 0: return if submitid: db.execute( "UPDATE submission SET favorites = favorites - 1 WHERE submitid = %(target)s", target=submitid, ) welcome.favorite_remove(db, userid, submitid=submitid, charid=charid, journalid=journalid)
def select(userid, submitid=None, charid=None, journalid=None): result = [] if submitid: statement = [ """ SELECT cm.commentid, cm.parentid, cm.userid, pr.username, lo.settings, cm.content, cm.unixtime, cm.settings, cm.indent, pr.config, cm.hidden_by FROM comments cm INNER JOIN profile pr USING (userid) INNER JOIN login lo USING (userid) WHERE cm.target_sub = %d """ % (submitid, ) ] else: statement = [ """ SELECT cm.commentid, cm.parentid, cm.userid, pr.username, lo.settings, cm.content, cm.unixtime, cm.settings, cm.indent, pr.config, cm.hidden_by FROM %scomment cm INNER JOIN profile pr USING (userid) INNER JOIN login lo USING (userid) WHERE cm.targetid = %i """ % ("submit" if submitid else "char" if charid else "journal", d.get_targetid(submitid, charid, journalid)) ] # moderators get to view hidden comments if userid not in staff.MODS: statement.append(" AND cm.settings !~ 'h'") if userid: statement.append(m.MACRO_IGNOREUSER % (userid, "cm")) statement.append(" ORDER BY COALESCE(cm.parentid, 0), cm.unixtime") query = d.execute("".join(statement)) for i, comment in enumerate(query): if comment[1]: break result.append({ "commentid": comment[0], "parentid": comment[1], "userid": comment[2], "username": comment[3], "status": "".join({"b", "s"} & set(comment[4])), "content": comment[5], "unixtime": comment[6], "settings": comment[7], "indent": comment[8], "hidden": 'h' in comment[7], "hidden_by": comment[10], }) _thread(query, result, i) media.populate_with_user_media(result) return result
def insert(userid, submitid=None, charid=None, journalid=None, parentid=None, content=None): if not submitid and not charid and not journalid: raise WeasylError("Unexpected") elif not content: raise WeasylError("commentInvalid") # Determine indent and parentuserid if parentid: query = d.execute( "SELECT userid, indent FROM %s WHERE commentid = %i", [ "comments" if submitid else "charcomment" if charid else "journalcomment", parentid ], options="single") if not query: raise WeasylError("Unexpected") indent = query[1] + 1 parentuserid = query[0] else: indent = 0 parentuserid = None # Determine otherid otherid = d.execute( "SELECT userid FROM %s WHERE %s = %i AND settings !~ 'h'", ["submission", "submitid", submitid] if submitid else ["character", "charid", charid] if charid else ["journal", "journalid", journalid], options="element") # Check permissions if not otherid: raise WeasylError("submissionRecordMissing") elif ignoreuser.check(otherid, userid): raise WeasylError("pageOwnerIgnoredYou") elif ignoreuser.check(userid, otherid): raise WeasylError("youIgnoredPageOwner") elif parentuserid and ignoreuser.check(parentuserid, userid): raise WeasylError("replyRecipientIgnoredYou") elif parentuserid and ignoreuser.check(userid, parentuserid): raise WeasylError("youIgnoredReplyRecipient") # Create comment if submitid: co = d.meta.tables['comments'] db = d.connect() commentid = db.scalar(co.insert().values(userid=userid, target_sub=submitid, parentid=parentid or None, content=content, unixtime=arrow.utcnow(), indent=indent).returning( co.c.commentid)) else: commentid = d.execute( "INSERT INTO %s (userid, targetid, parentid, " "content, unixtime, indent) VALUES (%i, %i, %i, '%s', %i, %i) RETURNING " "commentid", [ "charcomment" if charid else "journalcomment", userid, d.get_targetid(submitid, charid, journalid), parentid, content, d.get_time(), indent ], options="element") # Create notification if parentid and (userid != parentuserid): welcome.commentreply_insert(userid, commentid, parentuserid, parentid, submitid, charid, journalid) elif not parentid: # build a list of people this comment should notify # circular imports are cool and fun from weasyl.collection import find_owners notified = set(find_owners(submitid)) # check to see who we should deliver comment notifications to def can_notify(other): other_jsonb = d.get_profile_settings(other) allow_notify = other_jsonb.allow_collection_notifs ignored = ignoreuser.check(other, userid) return allow_notify and not ignored notified = set(filter(can_notify, notified)) # always give notification on own content notified.add(otherid) # don't give me a notification for my own comment notified.discard(userid) for other in notified: welcome.comment_insert(userid, commentid, other, submitid, charid, journalid) d.metric('increment', 'comments') return commentid
def associate(userid, tags, submitid=None, charid=None, journalid=None, preferred_tags_userid=None, optout_tags_userid=None): """ Associates searchtags with a content item. Parameters: userid: The userid of the user associating tags tags: A set of tags submitid: The ID number of a submission content item to associate ``tags`` to. (default: None) charid: The ID number of a character content item to associate ``tags`` to. (default: None) journalid: The ID number of a journal content item to associate ``tags`` to. (default: None) preferred_tags_userid: The ID number of a user to associate ``tags`` to for Preferred tags. (default: None) optout_tags_userid: The ID number of a user to associate ``tags`` to for Opt-Out tags. (default: None) Returns: A dict containing two elements. 1) ``add_failure_restricted_tags``, which contains a space separated string of tag titles which failed to be added to the content item due to the user or global restricted tag lists; and 2) ``remove_failure_owner_set_tags``, which contains a space separated string of tag titles which failed to be removed from the content item due to the owner of the aforementioned item prohibiting users from removing tags set by the content owner. If an element does not have tags, the element is set to None. If neither elements are set, the function returns None. """ targetid = d.get_targetid(submitid, charid, journalid) # Assign table, feature, ownerid if submitid: table, feature = "searchmapsubmit", "submit" ownerid = d.get_ownerid(submitid=targetid) elif charid: table, feature = "searchmapchar", "char" ownerid = d.get_ownerid(charid=targetid) elif journalid: table, feature = "searchmapjournal", "journal" ownerid = d.get_ownerid(journalid=targetid) elif preferred_tags_userid: table, feature = "artist_preferred_tags", "user" targetid = ownerid = preferred_tags_userid elif optout_tags_userid: table, feature = "artist_optout_tags", "user" targetid = ownerid = optout_tags_userid else: raise WeasylError("Unexpected") # Check permissions and invalid target if not ownerid: raise WeasylError("TargetRecordMissing") elif userid != ownerid and ("g" in d.get_config(userid) or preferred_tags_userid or optout_tags_userid): # disallow if user is forbidden from tagging, or trying to set artist tags on someone other than themselves raise WeasylError("InsufficientPermissions") elif ignoreuser.check(ownerid, userid): raise WeasylError("contentOwnerIgnoredYou") # Determine previous tagids, titles, and settings existing = d.engine.execute( "SELECT tagid, title, settings FROM {} INNER JOIN searchtag USING (tagid) WHERE targetid = %(target)s" .format(table), target=targetid).fetchall() # Retrieve tag titles and tagid pairs, for new (if any) and existing tags query = add_and_get_searchtags(tags) existing_tagids = {t.tagid for t in existing} entered_tagids = {t.tagid for t in query} # Assign added and removed added = entered_tagids - existing_tagids removed = existing_tagids - entered_tagids # enforce the limit on artist preference tags if preferred_tags_userid and (len(added) - len(removed) + len(existing)) > MAX_PREFERRED_TAGS: raise WeasylError("tooManyPreferenceTags") # Track which tags fail to be added or removed to later notify the user (Note: These are tagids at this stage) add_failure_restricted_tags = None remove_failure_owner_set_tags = None # If the modifying user is not the owner of the object, and is not staff, check user/global restriction lists if userid != ownerid and userid not in staff.MODS: user_rtags = set(query_user_restricted_tags(ownerid)) global_rtags = set(query_global_restricted_tags()) add_failure_restricted_tags = remove_restricted_tags( user_rtags | global_rtags, query) added -= add_failure_restricted_tags if len(add_failure_restricted_tags) == 0: add_failure_restricted_tags = None # Check removed artist tags if not can_remove_tags(userid, ownerid): existing_artist_tags = {t.tagid for t in existing if 'a' in t.settings} remove_failure_owner_set_tags = removed & existing_artist_tags removed.difference_update(existing_artist_tags) entered_tagids.update(existing_artist_tags) # Submission items use a different method of tag protection for artist tags; ignore them if submitid or len(remove_failure_owner_set_tags) == 0: remove_failure_owner_set_tags = None # Remove tags if removed: d.engine.execute( "DELETE FROM {} WHERE targetid = %(target)s AND tagid = ANY (%(removed)s)" .format(table), target=targetid, removed=list(removed)) if added: d.engine.execute( "INSERT INTO {} SELECT tag, %(target)s FROM UNNEST (%(added)s) AS tag" .format(table), target=targetid, added=list(added)) # preference/optout tags can only be set by the artist, so this settings column does not apply if userid == ownerid and not (preferred_tags_userid or optout_tags_userid): d.engine.execute( "UPDATE {} SET settings = settings || 'a' WHERE targetid = %(target)s AND tagid = ANY (%(added)s)" .format(table), target=targetid, added=list(added)) if submitid: d.engine.execute( 'INSERT INTO submission_tags (submitid, tags) VALUES (%(submission)s, %(tags)s) ' 'ON CONFLICT (submitid) DO UPDATE SET tags = %(tags)s', submission=submitid, tags=list(entered_tagids)) db = d.connect() db.execute(d.meta.tables['tag_updates'].insert().values( submitid=submitid, userid=userid, added=tag_array(added), removed=tag_array(removed))) if userid != ownerid: welcome.tag_update_insert(ownerid, submitid) files.append( "%stag.%s.%s.log" % (m.MACRO_SYS_LOG_PATH, feature, d.get_timestamp()), "-%sID %i -T %i -UID %i -X %s\n" % (feature[0].upper(), targetid, d.get_time(), userid, " ".join(tags))) # Return dict with any tag titles as a string that failed to be added or removed if add_failure_restricted_tags or remove_failure_owner_set_tags: if add_failure_restricted_tags: add_failure_restricted_tags = " ".join({ tag.title for tag in query if tag.tagid in add_failure_restricted_tags }) if remove_failure_owner_set_tags: remove_failure_owner_set_tags = " ".join({ tag.title for tag in existing if tag.tagid in remove_failure_owner_set_tags }) return { "add_failure_restricted_tags": add_failure_restricted_tags, "remove_failure_owner_set_tags": remove_failure_owner_set_tags } else: return None
def associate(userid, tags, submitid=None, charid=None, journalid=None): targetid = d.get_targetid(submitid, charid, journalid) # Assign table, feature, ownerid if submitid: table, feature = "searchmapsubmit", "submit" ownerid = d.get_ownerid(submitid=targetid) elif charid: table, feature = "searchmapchar", "char" ownerid = d.get_ownerid(charid=targetid) else: table, feature = "searchmapjournal", "journal" ownerid = d.get_ownerid(journalid=targetid) # Check permissions and invalid target if not ownerid: raise WeasylError("TargetRecordMissing") elif userid != ownerid and "g" in d.get_config(userid): raise WeasylError("InsufficientPermissions") elif ignoreuser.check(ownerid, userid): raise WeasylError("contentOwnerIgnoredYou") # Determine previous tags existing = d.engine.execute( "SELECT tagid, settings FROM {} WHERE targetid = %(target)s".format(table), target=targetid).fetchall() # Determine tag titles and tagids query = d.engine.execute( "SELECT tagid, title FROM searchtag WHERE title = ANY (%(tags)s)", tags=list(tags)).fetchall() newtags = list(tags - {x.title for x in query}) if newtags: query.extend( d.engine.execute( "INSERT INTO searchtag (title) SELECT * FROM UNNEST (%(newtags)s) AS title RETURNING tagid, title", newtags=newtags ).fetchall()) existing_tagids = {t.tagid for t in existing} entered_tagids = {t.tagid for t in query} # Assign added and removed added = entered_tagids - existing_tagids removed = existing_tagids - entered_tagids # Check removed artist tags if not can_remove_tags(userid, ownerid): existing_artist_tags = {t.tagid for t in existing if 'a' in t.settings} removed.difference_update(existing_artist_tags) entered_tagids.update(existing_artist_tags) # Remove tags if removed: d.engine.execute( "DELETE FROM {} WHERE targetid = %(target)s AND tagid = ANY (%(removed)s)".format(table), target=targetid, removed=list(removed)) if added: d.engine.execute( "INSERT INTO {} SELECT tag, %(target)s FROM UNNEST (%(added)s) AS tag".format(table), target=targetid, added=list(added)) if userid == ownerid: d.execute( "UPDATE %s SET settings = settings || 'a' WHERE targetid = %i AND tagid IN %s", [table, targetid, d.sql_number_list(list(added))]) if submitid: d.engine.execute( 'INSERT INTO submission_tags (submitid, tags) VALUES (%(submission)s, %(tags)s) ' 'ON CONFLICT (submitid) DO UPDATE SET tags = %(tags)s', submission=submitid, tags=list(entered_tagids)) db = d.connect() db.execute( d.meta.tables['tag_updates'].insert() .values(submitid=submitid, userid=userid, added=tag_array(added), removed=tag_array(removed))) if userid != ownerid: welcome.tag_update_insert(ownerid, submitid) files.append( "%stag.%s.%s.log" % (m.MACRO_SYS_LOG_PATH, feature, d.get_timestamp()), "-%sID %i -T %i -UID %i -X %s\n" % (feature[0].upper(), targetid, d.get_time(), userid, " ".join(tags)))
def associate(userid, tags, submitid=None, charid=None, journalid=None, preferred_tags_userid=None, optout_tags_userid=None): """ Associates searchtags with a content item. Parameters: userid: The userid of the user associating tags tags: A set of tags submitid: The ID number of a submission content item to associate ``tags`` to. (default: None) charid: The ID number of a character content item to associate ``tags`` to. (default: None) journalid: The ID number of a journal content item to associate ``tags`` to. (default: None) preferred_tags_userid: The ID number of a user to associate ``tags`` to for Preferred tags. (default: None) optout_tags_userid: The ID number of a user to associate ``tags`` to for Opt-Out tags. (default: None) Returns: A dict containing two elements. 1) ``add_failure_restricted_tags``, which contains a space separated string of tag titles which failed to be added to the content item due to the user or global restricted tag lists; and 2) ``remove_failure_owner_set_tags``, which contains a space separated string of tag titles which failed to be removed from the content item due to the owner of the aforementioned item prohibiting users from removing tags set by the content owner. If an element does not have tags, the element is set to None. If neither elements are set, the function returns None. """ targetid = d.get_targetid(submitid, charid, journalid) # Assign table, feature, ownerid if submitid: table, feature = "searchmapsubmit", "submit" ownerid = d.get_ownerid(submitid=targetid) elif charid: table, feature = "searchmapchar", "char" ownerid = d.get_ownerid(charid=targetid) elif journalid: table, feature = "searchmapjournal", "journal" ownerid = d.get_ownerid(journalid=targetid) elif preferred_tags_userid: table, feature = "artist_preferred_tags", "user" targetid = ownerid = preferred_tags_userid elif optout_tags_userid: table, feature = "artist_optout_tags", "user" targetid = ownerid = optout_tags_userid else: raise WeasylError("Unexpected") # Check permissions and invalid target if not ownerid: raise WeasylError("TargetRecordMissing") elif userid != ownerid and ("g" in d.get_config(userid) or preferred_tags_userid or optout_tags_userid): # disallow if user is forbidden from tagging, or trying to set artist tags on someone other than themselves raise WeasylError("InsufficientPermissions") elif ignoreuser.check(ownerid, userid): raise WeasylError("contentOwnerIgnoredYou") # Determine previous tagids, titles, and settings existing = d.engine.execute( "SELECT tagid, title, settings FROM {} INNER JOIN searchtag USING (tagid) WHERE targetid = %(target)s".format(table), target=targetid).fetchall() # Retrieve tag titles and tagid pairs, for new (if any) and existing tags query = add_and_get_searchtags(tags) existing_tagids = {t.tagid for t in existing} entered_tagids = {t.tagid for t in query} # Assign added and removed added = entered_tagids - existing_tagids removed = existing_tagids - entered_tagids # enforce the limit on artist preference tags if preferred_tags_userid and (len(added) - len(removed) + len(existing)) > MAX_PREFERRED_TAGS: raise WeasylError("tooManyPreferenceTags") # Track which tags fail to be added or removed to later notify the user (Note: These are tagids at this stage) add_failure_restricted_tags = None remove_failure_owner_set_tags = None # If the modifying user is not the owner of the object, and is not staff, check user/global restriction lists if userid != ownerid and userid not in staff.MODS: user_rtags = set(query_user_restricted_tags(ownerid)) global_rtags = set(query_global_restricted_tags()) add_failure_restricted_tags = remove_restricted_tags(user_rtags | global_rtags, query) added -= add_failure_restricted_tags if len(add_failure_restricted_tags) == 0: add_failure_restricted_tags = None # Check removed artist tags if not can_remove_tags(userid, ownerid): existing_artist_tags = {t.tagid for t in existing if 'a' in t.settings} remove_failure_owner_set_tags = removed & existing_artist_tags removed.difference_update(existing_artist_tags) entered_tagids.update(existing_artist_tags) # Submission items use a different method of tag protection for artist tags; ignore them if submitid or len(remove_failure_owner_set_tags) == 0: remove_failure_owner_set_tags = None # Remove tags if removed: d.engine.execute( "DELETE FROM {} WHERE targetid = %(target)s AND tagid = ANY (%(removed)s)".format(table), target=targetid, removed=list(removed)) if added: d.engine.execute( "INSERT INTO {} SELECT tag, %(target)s FROM UNNEST (%(added)s) AS tag".format(table), target=targetid, added=list(added)) # preference/optout tags can only be set by the artist, so this settings column does not apply if userid == ownerid and not (preferred_tags_userid or optout_tags_userid): d.execute( "UPDATE %s SET settings = settings || 'a' WHERE targetid = %i AND tagid IN %s", [table, targetid, d.sql_number_list(list(added))]) if submitid: d.engine.execute( 'INSERT INTO submission_tags (submitid, tags) VALUES (%(submission)s, %(tags)s) ' 'ON CONFLICT (submitid) DO UPDATE SET tags = %(tags)s', submission=submitid, tags=list(entered_tagids)) db = d.connect() db.execute( d.meta.tables['tag_updates'].insert() .values(submitid=submitid, userid=userid, added=tag_array(added), removed=tag_array(removed))) if userid != ownerid: welcome.tag_update_insert(ownerid, submitid) files.append( "%stag.%s.%s.log" % (m.MACRO_SYS_LOG_PATH, feature, d.get_timestamp()), "-%sID %i -T %i -UID %i -X %s\n" % (feature[0].upper(), targetid, d.get_time(), userid, " ".join(tags))) # Return dict with any tag titles as a string that failed to be added or removed if add_failure_restricted_tags or remove_failure_owner_set_tags: if add_failure_restricted_tags: add_failure_restricted_tags = " ".join({tag.title for tag in query if tag.tagid in add_failure_restricted_tags}) if remove_failure_owner_set_tags: remove_failure_owner_set_tags = " ".join({tag.title for tag in existing if tag.tagid in remove_failure_owner_set_tags}) return {"add_failure_restricted_tags": add_failure_restricted_tags, "remove_failure_owner_set_tags": remove_failure_owner_set_tags} else: return None
def remove(userid, submitid=None, charid=None, journalid=None): d.execute("DELETE FROM favorite WHERE (userid, targetid, type) = (%i, %i, '%s')", [userid, d.get_targetid(submitid, charid, journalid), "s" if submitid else "f" if charid else "j"]) welcome.favorite_remove(userid, submitid=submitid, charid=charid, journalid=journalid)
def insert(userid, submitid=None, charid=None, journalid=None, parentid=None, content=None): if not submitid and not charid and not journalid: raise WeasylError("Unexpected") elif not content: raise WeasylError("commentInvalid") # Determine indent and parentuserid if parentid: query = d.execute("SELECT userid, indent FROM %s WHERE commentid = %i", ["comments" if submitid else "charcomment" if charid else "journalcomment", parentid], options="single") if not query: raise WeasylError("Unexpected") indent = query[1] + 1 parentuserid = query[0] else: indent = 0 parentuserid = None # Determine otherid otherid = d.execute("SELECT userid FROM %s WHERE %s = %i AND settings !~ 'h'", ["submission", "submitid", submitid] if submitid else ["character", "charid", charid] if charid else ["journal", "journalid", journalid], options="element") # Check permissions if not otherid: raise WeasylError("submissionRecordMissing") elif ignoreuser.check(otherid, userid): raise WeasylError("pageOwnerIgnoredYou") elif ignoreuser.check(userid, otherid): raise WeasylError("youIgnoredPageOwner") elif parentuserid and ignoreuser.check(parentuserid, userid): raise WeasylError("replyRecipientIgnoredYou") elif parentuserid and ignoreuser.check(userid, parentuserid): raise WeasylError("youIgnoredReplyRecipient") # Create comment if submitid: co = d.meta.tables['comments'] db = d.connect() commentid = db.scalar( co.insert() .values(userid=userid, target_sub=submitid, parentid=parentid or None, content=content, unixtime=arrow.utcnow(), indent=indent) .returning(co.c.commentid)) else: commentid = d.execute( "INSERT INTO %s (userid, targetid, parentid, " "content, unixtime, indent) VALUES (%i, %i, %i, '%s', %i, %i) RETURNING " "commentid", [ "charcomment" if charid else "journalcomment", userid, d.get_targetid(submitid, charid, journalid), parentid, content, d.get_time(), indent ], options="element") # Create notification if parentid and (userid != parentuserid): welcome.commentreply_insert(userid, commentid, parentuserid, parentid, submitid, charid, journalid) elif not parentid: # build a list of people this comment should notify # circular imports are cool and fun from weasyl.collection import find_owners notified = set(find_owners(submitid)) # check to see who we should deliver comment notifications to def can_notify(other): other_jsonb = d.get_profile_settings(other) allow_notify = other_jsonb.allow_collection_notifs ignored = ignoreuser.check(other, userid) return allow_notify and not ignored notified = set(filter(can_notify, notified)) # always give notification on own content notified.add(otherid) # don't give me a notification for my own comment notified.discard(userid) for other in notified: welcome.comment_insert(userid, commentid, other, submitid, charid, journalid) d.metric('increment', 'comments') return commentid
def insert(userid, submitid=None, charid=None, journalid=None, updateid=None, parentid=None, content=None): if submitid: table = "comments" elif charid: table = "charcomment" elif journalid: table = "journalcomment" elif updateid: table = "siteupdatecomment" else: raise WeasylError("Unexpected") if not content: raise WeasylError("commentInvalid") # Determine parent userid if parentid: parentuserid = d.engine.scalar( "SELECT userid FROM {table} WHERE commentid = %(parent)s".format( table=table), parent=parentid, ) if parentuserid is None: raise WeasylError("Unexpected") else: if updateid: parentid = None # parentid == 0 parentuserid = None if updateid: otherid = d.engine.scalar( "SELECT userid FROM siteupdate WHERE updateid = %(update)s", update=updateid) if not otherid: raise WeasylError("submissionRecordMissing") else: # Determine the owner of the target otherid = d.engine.scalar( "SELECT userid FROM %s WHERE %s = %i AND settings !~ 'h'" % (("submission", "submitid", submitid) if submitid else ("character", "charid", charid) if charid else ("journal", "journalid", journalid))) # Check permissions if not otherid: raise WeasylError("submissionRecordMissing") elif ignoreuser.check(otherid, userid): raise WeasylError("pageOwnerIgnoredYou") elif ignoreuser.check(userid, otherid): raise WeasylError("youIgnoredPageOwner") if parentuserid and ignoreuser.check(parentuserid, userid): raise WeasylError("replyRecipientIgnoredYou") elif parentuserid and ignoreuser.check(userid, parentuserid): raise WeasylError("youIgnoredReplyRecipient") # Create comment if submitid: co = d.meta.tables['comments'] db = d.connect() commentid = db.scalar(co.insert().values( userid=userid, target_sub=submitid, parentid=parentid or None, content=content, unixtime=arrow.utcnow()).returning(co.c.commentid)) elif updateid: commentid = d.engine.scalar( "INSERT INTO siteupdatecomment (userid, targetid, parentid, content)" " VALUES (%(user)s, %(update)s, %(parent)s, %(content)s)" " RETURNING commentid", user=userid, update=updateid, parent=parentid, content=content, ) else: commentid = d.engine.scalar( "INSERT INTO {table} (userid, targetid, parentid, content, unixtime)" " VALUES (%(user)s, %(target)s, %(parent)s, %(content)s, %(now)s)" " RETURNING commentid".format( table="charcomment" if charid else "journalcomment"), user=userid, target=d.get_targetid(charid, journalid), parent=parentid or 0, content=content, now=d.get_time(), ) # Create notification if parentid and (userid != parentuserid): welcome.commentreply_insert(userid, commentid, parentuserid, parentid, submitid, charid, journalid, updateid) elif not parentid: # build a list of people this comment should notify # circular imports are cool and fun from weasyl.collection import find_owners notified = set(find_owners(submitid)) # check to see who we should deliver comment notifications to def can_notify(other): other_jsonb = d.get_profile_settings(other) allow_notify = other_jsonb.allow_collection_notifs ignored = ignoreuser.check(other, userid) return allow_notify and not ignored notified = set(filter(can_notify, notified)) # always give notification on own content notified.add(otherid) # don't give me a notification for my own comment notified.discard(userid) for other in notified: welcome.comment_insert(userid, commentid, other, submitid, charid, journalid, updateid) d.metric('increment', 'comments') return commentid
def associate(userid, tags, submitid=None, charid=None, journalid=None): targetid = d.get_targetid(submitid, charid, journalid) # Assign table, feature, ownerid if submitid: table, feature = "searchmapsubmit", "submit" ownerid = d.get_ownerid(submitid=targetid) elif charid: table, feature = "searchmapchar", "char" ownerid = d.get_ownerid(charid=targetid) else: table, feature = "searchmapjournal", "journal" ownerid = d.get_ownerid(journalid=targetid) # Check permissions and invalid target if not ownerid: raise WeasylError("TargetRecordMissing") elif userid != ownerid and "g" in d.get_config(userid): raise WeasylError("InsufficientPermissions") elif ignoreuser.check(ownerid, userid): raise WeasylError("contentOwnerIgnoredYou") # Determine previous tags existing = d.engine.execute( "SELECT tagid, settings FROM {} WHERE targetid = %(target)s".format(table), target=targetid).fetchall() # Determine tag titles and tagids query = d.engine.execute( "SELECT tagid, title FROM searchtag WHERE title = ANY (%(tags)s)", tags=list(tags)).fetchall() newtags = list(tags - {x.title for x in query}) if newtags: query.extend( d.engine.execute( "INSERT INTO searchtag (title) SELECT * FROM UNNEST (%(newtags)s) AS title RETURNING tagid, title", newtags=newtags ).fetchall()) existing_tagids = {t.tagid for t in existing} entered_tagids = {t.tagid for t in query} # Assign added and removed added = entered_tagids - existing_tagids removed = existing_tagids - entered_tagids # Check removed artist tags if not can_remove_tags(userid, ownerid): existing_artist_tags = {t.tagid for t in existing if 'a' in t.settings} removed.difference_update(existing_artist_tags) entered_tagids.update(existing_artist_tags) # Remove tags if removed: d.engine.execute( "DELETE FROM {} WHERE targetid = %(target)s AND tagid = ANY (%(removed)s)".format(table), target=targetid, removed=list(removed)) if added: d.execute("INSERT INTO %s VALUES %s" % (table, d.sql_number_series([[i, targetid] for i in added]))) if userid == ownerid: d.execute( "UPDATE %s SET settings = settings || 'a' WHERE targetid = %i AND tagid IN %s", [table, targetid, d.sql_number_list(list(added))]) if submitid: try: d.engine.execute( 'INSERT INTO submission_tags (submitid, tags) VALUES (%(submission)s, %(tags)s)', submission=submitid, tags=list(entered_tagids)) except PostgresError: result = d.engine.execute( 'UPDATE submission_tags SET tags = %(tags)s WHERE submitid = %(submission)s', submission=submitid, tags=list(entered_tagids)) assert result.rowcount == 1 db = d.connect() db.execute( d.meta.tables['tag_updates'].insert() .values(submitid=submitid, userid=userid, added=tag_array(added), removed=tag_array(removed))) if userid != ownerid: welcome.tag_update_insert(ownerid, submitid) files.append( "%stag.%s.%s.log" % (m.MACRO_SYS_LOG_PATH, feature, d.get_timestamp()), "-%sID %i -T %i -UID %i -X %s\n" % (feature[0].upper(), targetid, d.get_time(), userid, " ".join(tags)))