class Character(Base): __table__ = tables.character owner = relationship(Login, backref='characters') with clauses_for(__table__) as c: is_hidden = c('hidden') @property def title(self): return self.char_name def legacy_path(self, mod=False): """ Create the weasyl-old relative URL for a character. Parameters: mod (bool): Whether or not to suffix ``?anyway=true`` on the URL for moderators. Returns: The relative URL. """ ret = '/character/%d/%s' % (self.charid, slug_for(self.char_name)) if mod: ret += '?anyway=true' return ret
class Folder(Base): __table__ = tables.folder owner = relationship(Login, backref='folders') submissions = relationship(Submission, backref=backref('folder', uselist=False)) with clauses_for(__table__) as c: is_featured = c('featured-filter')
class SubmissionTag(Base): __table__ = tables.searchmapsubmit tag_object = relationship(Tag, uselist=False, lazy='joined') tag = association_proxy('tag_object', 'title') with clauses_for(__table__) as c: is_artist_tag = c('artist-tag') def __repr__(self): return '<SubmissionTag %r -> %r (%r)>' % (self.targetid, self.tagid, self.settings)
class Submission(Base): __table__ = tables.submission owner = relationship(Login) with clauses_for(__table__) as c: is_hidden = c('hidden') is_friends_only = c('friends-only') is_critique = c('critique') is_google_doc = c('embed-type', 'google-drive') is_other_embed = c('embed-type', 'other') def legacy_path(self, mod=False): """ Create the weasyl-old relative URL for a submission. Parameters: mod (bool): Whether or not to suffix ``?anyway=true`` on the URL for moderators. Returns: The relative URL. """ ret = '/submission/%d/%s' % (self.submitid, slug_for(self.title)) if mod: ret += '?anyway=true' return ret @reify def media(self): return get_submission_media(self.submitid) @reify def submission_media(self): ret = self.media.get('submission') if ret: return ret[0] return None @reify def cover_media(self): ret = self.media.get('cover') # TODO: this should be unnecessary if not ret and self.submission_media: ret = self.submission_media['described'].get('cover') if ret: return ret[0] return None
class Journal(Base): __table__ = tables.journal owner = relationship(Login, backref='journals') with clauses_for(__table__) as c: is_hidden = c('hidden') def legacy_path(self, mod=False): """ Create the weasyl-old relative URL for a journal. Parameters: mod (bool): Whether or not to suffix ``?anyway=true`` on the URL for moderators. Returns: The relative URL. """ ret = '/journal/%d/%s' % (self.journalid, slug_for(self.title)) if mod: ret += '?anyway=true' return ret
class Submission(Base): __table__ = tables.submission now = staticmethod(arrow.get) owner = relationship(Login, backref='submissions') tag_objects = relationship(Tag, secondary=SubmissionTag.__table__) tags = association_proxy('tag_objects', 'title') with clauses_for(__table__) as c: is_hidden = c('hidden') is_friends_only = c('friends-only') is_critique = c('critique') is_google_doc = c('embed-type', 'google-drive') is_other_embed = c('embed-type', 'other') def _comment_criteria(self): return {'target_sub': self.submitid} def __json__(self, request): return { 'title': self.title, 'rating': self.rating.name, 'media': minimize_media(request, getattr(self, 'media', None)), } def canonical_path(self, request, operation='view', with_slug=None, mod=False): if with_slug is None: with_slug = operation == 'view' parts = ['submissions', str(self.submitid), operation] if mod: parts.insert(2, 'mod') if with_slug: parts.append(slug_for(self.title)) return request.resource_path(None, *parts) def legacy_path(self, mod=False): """ Create the weasyl-old relative URL for a submission. Parameters: mod (bool): Whether or not to suffix ``?anyway=true`` on the URL for moderators. Returns: The relative URL. """ ret = '/submission/%d/%s' % (self.submitid, slug_for(self.title)) if mod: ret += '?anyway=true' return ret @reify def media(self): from libweasyl.media import get_submission_media return get_submission_media(self.submitid) @reify def submission_media(self): ret = self.media.get('submission') if ret: return ret[0] return None @reify def cover_media(self): ret = self.media.get('cover') if not ret and self.submission_media: ret = self.submission_media['described'].get('cover') if ret: return ret[0] return None @reify def thumbnail_media(self): # TODO: Fix this to use the right keys, post-migration. ret = self.media.get('thumbnail-legacy') or self.media.get('thumbnail') if ret: return ret[0] return None @reify def popup_media(self): ret = self.media.get('popup') if not ret and self.thumbnail_media: return self.thumbnail_media elif ret: return ret[0] return None @validator() def validate_rating(self, value, old_value, initiator): if self.owner is None: # 6 determined experimentally from the current version of SQLAlchemy. warnings.warn( 'tried to set the rating on a Submission without an owner', RuntimeWarning, stacklevel=6) return if not self.owner.is_permitted_rating(value): raise RatingExceeded( 'The rating %s exceeds the rating limit allowed for %s.' % (value.nice_name, self.owner.profile.username)) def set_tags(self, new_tags, done_by_artist=False): new_tags = set(new_tags) current_tags = SubmissionTag.query.filter_by( targetid=self.submitid).all() current_tags_set = {t.tag for t in current_tags} tags_to_add = new_tags - current_tags_set tags_to_remove = current_tags_set - new_tags if tags_to_add: entered_tags = Tag.query.filter(Tag.title.in_(tags_to_add)).all() else: entered_tags = [] entered_tags_set = {t.title for t in entered_tags} tag_ids_to_add = {t.tagid for t in entered_tags} tags_to_enter = tags_to_add - entered_tags_set if tags_to_enter: q = (Tag.__table__.insert().values([{ 'title': t } for t in tags_to_enter]).returning(Tag.tagid)) tag_ids_to_add.update( t for t, in self.dbsession.execute(q).fetchall()) if tag_ids_to_add: settings = CharSettings(set(), {}, {}) if done_by_artist: settings.mutable_settings.add('artist-tag') q = (SubmissionTag.__table__.insert().values([{ 'targetid': self.submitid, 'tagid': t, 'settings': settings } for t in tag_ids_to_add])) self.dbsession.execute(q) if tags_to_remove: tag_ids_to_remove = { t.tagid for t in current_tags if t.tag in tags_to_remove } q = (SubmissionTag.__table__.delete().where( SubmissionTag.targetid == self.submitid).where( SubmissionTag.tagid.in_(tag_ids_to_remove))) self.dbsession.execute(q) @classmethod def create(cls, owner, title, rating, description, category, subtype, folder, tags, friends_only=False, critique_requested=False, submission_data=None, cover_data=None, thumbnail_data=None, embed_link=None, submission_size_limit=None): from libweasyl.media import fetch_or_create_media_item, make_cover_media_item from libweasyl.models.media import SubmissionMediaLink now = cls.now() inst = cls(owner=owner, title=title, content=description, subtype=subtype, folder=folder, settings=None, unixtime=now, sorttime=now) # must be set after 'owner' for validation. inst.rating = rating inst.is_friends_only = friends_only inst.is_critique = critique_requested submission_media_item = cover_media_item = thumbnail_media_item = thumbnail_source_media_item = None if embed_link is None: if submission_data is None: raise InvalidData( "A submission file or an embed link is required.") submission_decoded, submission_format = file_type_for_category( submission_data, category) if submission_size_limit is None: submission_size_limit = DEFAULT_LIMITS[submission_format] if len(submission_data) > submission_size_limit: raise SubmissionFileTooLarge( "The submission file uploaded exceeds the limit of %g mebibytes." % (submission_size_limit / MEBIBYTE)) submission_image = None if category == Category.visual: submission_image = submission_decoded submission_media_item = fetch_or_create_media_item( submission_data, file_type=submission_format, im=submission_image) elif category == Category.visual: raise ValueError('embedded visual submissions are not supported') elif submission_data is not None: raise InvalidData( "You may not submit both submission data and an embed link.") elif category == Category.literary: inst.is_google_doc = True inst.google_doc = GoogleDocEmbed(embed_url=embed_link) elif category == Category.multimedia: inst.is_other_embed = True inst.content = '%s\n%s' % (embed_link, inst.content) else: raise ValueError('unknown submission category', category) if category == Category.visual: cover_media_item = submission_media_item.ensure_cover_image( submission_image) thumbnail_media_item = submission_media_item.make_thumbnail( submission_image) elif cover_data is not None: cover_media_item = make_cover_media_item(cover_data) if thumbnail_data is not None: thumbnail_source_media_item = make_cover_media_item(thumbnail_data) cls.dbsession.add(inst) cls.dbsession.flush() # The submission is in the database now, so tags and media links can be set. inst.set_tags(tags, done_by_artist=True) if submission_media_item is not None: SubmissionMediaLink.make_or_replace_link(inst.submitid, 'submission', submission_media_item) if category == Category.visual: popup_media_item = submission_media_item.make_popup( submission_image) SubmissionMediaLink.make_or_replace_link( inst.submitid, 'popup', popup_media_item) if cover_media_item is not None: SubmissionMediaLink.make_or_replace_link(inst.submitid, 'cover', cover_media_item) if thumbnail_media_item is not None: SubmissionMediaLink.make_or_replace_link(inst.submitid, 'thumbnail', thumbnail_media_item) if thumbnail_source_media_item is not None: SubmissionMediaLink.make_or_replace_link( inst.submitid, 'thumbnail-source', thumbnail_source_media_item) return inst
class Report(Base): __table__ = tables.report with clauses_for(__table__) as c: is_under_review = c('under-review') _target_user = relationship(Login, foreign_keys=[__table__.c.target_user], backref='profile_reports') _target_sub = relationship(Submission, backref='reports') _target_char = relationship(Character, backref='reports') _target_journal = relationship(Journal, backref='reports') _target_comment = relationship(Comment, backref='reports') comments = relationship(ReportComment, backref=backref('report', uselist=False)) owner = relationship(Login, foreign_keys=[__table__.c.closerid], backref='owned_reports') @property def target(self): if self.target_user: return self._target_user elif self.target_sub: return self._target_sub elif self.target_char: return self._target_char elif self.target_journal: return self._target_journal elif self.target_comment: return self._target_comment else: raise ValueError( 'no target profile, user, character, journal, or comment') @property def target_type(self): if self.target_user: return 'profile' elif self.target_sub: return 'submission' elif self.target_char: return 'character' elif self.target_journal: return 'journal' elif self.target_comment: return 'comment' else: raise ValueError( 'no target profile, user, character, journal, or comment') @property def status(self): if self.closerid is None: return 'open' elif self.is_under_review: return 'review' else: return 'closed' @hybrid_property def is_closed(self): return self.closerid is not None and not self.is_under_review @is_closed.expression def is_closed(cls): return (None != cls.closerid) & (~cls.is_under_review) def related_reports(self): cls = type(self) q = (cls.query.filter_by(target_user=self.target_user, target_sub=self.target_sub, target_char=self.target_char, target_journal=self.target_journal, target_comment=self.target_comment).filter( cls.reportid != self.reportid).order_by( cls.opened_at.asc())) return q.all()
class Friendship(Base): __table__ = tables.frienduser with clauses_for(__table__) as c: is_pending = c('pending')